public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
To: git-commits@fedoraproject.org
Subject: [rpms/pcs] f43: pcs-0.12.3-1
Date: Wed, 16 Sep 2026 12:21:14 GMT	[thread overview]
Message-ID: <178956127478.1.207924623514939288.rpms-pcs-75c63d554b8b@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/pcs
            Branch : f43
            Commit : 75c63d554b8b484c190572133f3d9898e74b2a68
            Author : Michal Pospíšil <mpospisi@redhat.com>
            Date   : 2026-09-11T20:05:50+02:00
            Stats  : +200/-1322 in 9 file(s)
            URL    : https://src.fedoraproject.org/rpms/pcs/c/75c63d554b8b484c190572133f3d9898e74b2a68?branch=f43

            Log:
            pcs-0.12.3-1

- Rebased pcs to the newest major version (see CHANGELOG.md)
- Updated standalone web UI and HA Cluster Management Cockpit application to pcs-web-ui 0.1.25 (see CHANGELOG_WUI.md)
- pcs no longer depends on rubygems ethon and ffi, rubygem curb is used instead

---
diff --git a/.gitignore b/.gitignore
index 69d70b5..349c1c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -130,3 +130,6 @@
 /pcs-0.12.2.tar.gz
 /pcs-web-ui-0.1.24.3.tar.gz
 /pcs-web-ui-node-modules-0.1.24.3.tar.xz
+/pcs-0.12.3.1.tar.gz
+/pcs-web-ui-0.1.25.tar.gz
+/pcs-web-ui-node-modules-0.1.25.tar.xz

diff --git a/bz2458608-01-fix-a-crash-when-determining-terminal-size.patch b/bz2458608-01-fix-a-crash-when-determining-terminal-size.patch
deleted file mode 100644
index acf547a..0000000
--- a/bz2458608-01-fix-a-crash-when-determining-terminal-size.patch
+++ /dev/null
@@ -1,197 +0,0 @@
-From e6db4a23494a68f945a5987b38b1571c8c59b0b1 Mon Sep 17 00:00:00 2001
-From: Tomas Jelinek <tojeline@redhat.com>
-Date: Mon, 20 Apr 2026 17:06:05 +0200
-Subject: [PATCH 1/2] fix a crash when determining terminal size
-
----
- pcs/resource.py | 34 ++++------------------------------
- pcs/stonith.py  | 30 ++++++++----------------------
- pcs/utils.py    | 25 -------------------------
- 3 files changed, 12 insertions(+), 77 deletions(-)
-
-diff --git a/pcs/resource.py b/pcs/resource.py
-index f81444928..60b75aa52 100644
---- a/pcs/resource.py
-+++ b/pcs/resource.py
-@@ -2,7 +2,6 @@
- import json
- import re
- import sys
--import textwrap
- from functools import partial
- from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional
- from xml.dom.minidom import parseString
-@@ -12,6 +11,7 @@ import pcs.lib.resource_agent as lib_ra
- from pcs import constraint, utils
- from pcs.cli.cluster_property.output import PropertyConfigurationFacade
- from pcs.cli.common.errors import CmdLineInputError
-+from pcs.cli.common.output import format_wrap_for_terminal
- from pcs.cli.common.parse_args import (
-     FUTURE_OPTION,
-     OUTPUT_FORMAT_VALUE_CMD,
-@@ -505,12 +505,10 @@ def resource_list_available(
-         name = agent_info["name"]
-         shortdesc = agent_info["shortdesc"]
-         if shortdesc:
-+            normalized_desc = " ".join(shortdesc.split())
-             print(
--                "{0} - {1}".format(
--                    name,
--                    _format_desc(
--                        len(name + " - "), shortdesc.replace("\n", " ")
--                    ),
-+                "\n".join(
-+                    format_wrap_for_terminal(f"{name} - {normalized_desc}")
-                 )
-             )
-         else:
-@@ -558,30 +556,6 @@ def resource_list_options(
-     )
- 
- 
--# Return the string formatted with a line length of terminal width  and indented
--def _format_desc(indentation: int, desc: str) -> str:
--    """
--    Commandline options: no options
--    """
--    desc = " ".join(desc.split())
--    dummy_rows, columns = utils.getTerminalSize()
--    columns = max(int(columns), 40)
--    afterindent = columns - indentation
--    if afterindent < 1:
--        afterindent = columns
--
--    output = ""
--    first = True
--    for line in textwrap.wrap(desc, afterindent):
--        if not first:
--            output += " " * indentation
--        output += line
--        output += "\n"
--        first = False
--
--    return output.rstrip()
--
--
- def resource_create(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:  # noqa: PLR0912
-     """
-     Options:
-diff --git a/pcs/stonith.py b/pcs/stonith.py
-index 587405048..b029a393e 100644
---- a/pcs/stonith.py
-+++ b/pcs/stonith.py
-@@ -1,15 +1,10 @@
- import json
--from typing import (
--    Any,
--    Optional,
--)
-+from typing import Any, Optional
- 
--from pcs import (
--    resource,
--    utils,
--)
-+from pcs import resource, utils
- from pcs.cli.common import parse_args
- from pcs.cli.common.errors import CmdLineInputError
-+from pcs.cli.common.output import format_wrap_for_terminal
- from pcs.cli.common.parse_args import (
-     Argv,
-     InputModifiers,
-@@ -17,9 +12,7 @@ from pcs.cli.common.parse_args import (
-     ensure_unique_args,
- )
- from pcs.cli.fencing_topology import target_type_map_cli_to_lib
--from pcs.cli.reports.output import (
--    print_to_stderr,
--)
-+from pcs.cli.reports.output import print_to_stderr
- from pcs.cli.resource.output import resource_agent_metadata_to_text
- from pcs.cli.resource.parse_args import (
-     parse_primitive as parse_primitive_resource,
-@@ -33,11 +26,7 @@ from pcs.common.pacemaker.resource.list import (
-     get_stonith_resources_ids,
- )
- from pcs.common.resource_agent.dto import ResourceAgentNameDto
--from pcs.common.str_tools import (
--    format_list,
--    format_plural,
--    indent,
--)
-+from pcs.common.str_tools import format_list, format_plural, indent
- 
- 
- def stonith_status_cmd(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
-@@ -81,13 +70,10 @@ def stonith_list_available(
-         name = agent_info["type"]
-         shortdesc = agent_info["shortdesc"]
-         if shortdesc:
-+            normalized_desc = " ".join(shortdesc.split())
-             print(
--                "{0} - {1}".format(
--                    name,
--                    # pylint: disable=protected-access
--                    resource._format_desc(  # noqa: SLF001
--                        len(name + " - "), shortdesc.replace("\n", " ")
--                    ),
-+                "\n".join(
-+                    format_wrap_for_terminal(f"{name} - {normalized_desc}")
-                 )
-             )
-         else:
-diff --git a/pcs/utils.py b/pcs/utils.py
-index 3d2b8221f..58d08fb83 100644
---- a/pcs/utils.py
-+++ b/pcs/utils.py
-@@ -1,18 +1,15 @@
- # pylint: disable=too-many-lines
- import base64
--import fcntl
- import getpass
- import json
- import logging
- import os
- import re
- import signal
--import struct
- import subprocess
- import sys
- import tarfile
- import tempfile
--import termios
- import threading
- import time
- import xml.dom.minidom
-@@ -1785,28 +1782,6 @@ def set_node_attribute(prop, value, node):
-         err("unable to set attribute %s\n%s" % (prop, o))
- 
- 
--def getTerminalSize(fd=1):
--    """
--    Returns height and width of current terminal. First tries to get
--    size via termios.TIOCGWINSZ, then from environment. Defaults to 25
--    lines x 80 columns if both methods fail.
--
--    :param fd: file descriptor (default: 1=stdout)
--
--    Commandline options: no options
--    """
--    try:
--        hw = struct.unpack(
--            str("hh"), fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234")
--        )
--    except OSError:
--        try:
--            hw = (os.environ["LINES"], os.environ["COLUMNS"])
--        except KeyError:
--            hw = (25, 80)
--    return hw
--
--
- def get_terminal_input(message=None):
-     """
-     Commandline options: no options
--- 
-2.54.0
-

diff --git a/bz2461143-01-Update-constraint-order-printout.patch b/bz2461143-01-Update-constraint-order-printout.patch
deleted file mode 100644
index 1fd15f6..0000000
--- a/bz2461143-01-Update-constraint-order-printout.patch
+++ /dev/null
@@ -1,436 +0,0 @@
-From cd7d6ca749e77d70611fb8b0fb92600bb5bf5af2 Mon Sep 17 00:00:00 2001
-From: Matej Slivka <mslivka@redhat.com>
-Date: Thu, 7 May 2026 12:18:57 +0200
-Subject: [PATCH 2/2] Update constraint order printout
-
----
- pcs/cli/constraint/output/set.py              |  9 ++--
- pcs_test/Makefile.am                          |  2 +
- pcs_test/resources/cib-all.xml                |  8 ++--
- pcs_test/resources/constraint-commands        | 24 +++++-----
- .../tier0/cli/constraint/output/__init__.py   |  0
- .../tier0/cli/constraint/output/test_set.py   | 45 +++++++++++++++++++
- pcs_test/tier0/cli/constraint/test_command.py |  2 +-
- pcs_test/tier1/constraint/test_config.py      | 36 +++++++--------
- pcs_test/tier1/legacy/test_constraints.py     | 16 +++----
- pcs_test/tools/constraints_dto.py             |  8 ++--
- 10 files changed, 97 insertions(+), 53 deletions(-)
- create mode 100644 pcs_test/tier0/cli/constraint/output/__init__.py
- create mode 100644 pcs_test/tier0/cli/constraint/output/test_set.py
-
-diff --git a/pcs/cli/constraint/output/set.py b/pcs/cli/constraint/output/set.py
-index 5395ebf7a..5335fdf47 100644
---- a/pcs/cli/constraint/output/set.py
-+++ b/pcs/cli/constraint/output/set.py
-@@ -1,7 +1,4 @@
--from typing import (
--    Optional,
--    Sequence,
--)
-+from typing import Optional, Sequence
- 
- from pcs.cli.common.output import (
-     INDENT_STEP,
-@@ -12,7 +9,7 @@ from pcs.cli.common.output import (
- from pcs.cli.reports.output import warn
- from pcs.common.pacemaker.constraint import CibResourceSetDto
- from pcs.common.str_tools import (
--    format_list,
-+    format_list_dont_sort,
-     format_optional,
-     indent,
-     pairs_to_text,
-@@ -56,7 +53,7 @@ def resource_set_to_text(
-     ]
-     set_options = [
-         "Resources: {resources}".format(
--            resources=format_list(resource_set_dto.resources_ids)
-+            resources=format_list_dont_sort(resource_set_dto.resources_ids)
-         )
-     ] + pairs_to_text(_resource_set_options_to_pairs(resource_set_dto))
-     output.extend(indent(set_options, indent_step=INDENT_STEP))
-diff --git a/pcs_test/Makefile.am b/pcs_test/Makefile.am
-index d54043ec0..64df39b80 100644
---- a/pcs_test/Makefile.am
-+++ b/pcs_test/Makefile.am
-@@ -99,7 +99,9 @@ EXTRA_DIST		= \
- 			  tier0/cli/constraint/__init__.py \
- 			  tier0/cli/constraint/location/__init__.py \
- 			  tier0/cli/constraint/location/test_command.py \
-+			  tier0/cli/constraint/output/__init__.py \
- 			  tier0/cli/constraint/output/test_all.py \
-+			  tier0/cli/constraint/output/test_set.py \
- 			  tier0/cli/constraint/test_command.py \
- 			  tier0/cli/constraint/test_parse_args.py \
- 			  tier0/cli/constraint_colocation/__init__.py \
-diff --git a/pcs_test/resources/cib-all.xml b/pcs_test/resources/cib-all.xml
-index 2964a1857..8a9c5981a 100644
---- a/pcs_test/resources/cib-all.xml
-+++ b/pcs_test/resources/cib-all.xml
-@@ -175,12 +175,12 @@
-       </rsc_location>
-       <rsc_order first="R7" then="G2" score="-123" require-all="false" first-action="stop" then-action="stop" symmetrical="false" id="order-R7-G2-mandatory"/>
-       <rsc_order first="G2" then="B2" kind="Optional" first-action="start" then-action="start" id="order-G2-B2-Optional"/>
--      <rsc_order kind="Optional" id="order_set_B2R6-cloneSe">
--        <resource_set action="stop" require-all="false" id="order_set_B2R6-cloneSe_set">
--          <resource_ref id="B2"/>
-+      <rsc_order kind="Optional" id="order_set_R6-cloneB2Se">
-+        <resource_set action="stop" require-all="false" id="order_set_R6-cloneB2Se_set">
-           <resource_ref id="R6-clone"/>
-+          <resource_ref id="B2"/>
-         </resource_set>
--        <resource_set action="promote" sequential="false" id="order_set_B2R6-cloneSe_set-1">
-+        <resource_set action="promote" sequential="false" id="order_set_R6-cloneB2Se_set-1">
-           <resource_ref id="G1-clone"/>
-         </resource_set>
-       </rsc_order>
-diff --git a/pcs_test/resources/constraint-commands b/pcs_test/resources/constraint-commands
-index 8ac6b574e..5a293cffc 100644
---- a/pcs_test/resources/constraint-commands
-+++ b/pcs_test/resources/constraint-commands
-@@ -1,18 +1,18 @@
- # set of commands to generate various constraints configuration
--pcs -- constraint location add location-R7-non-existing-node--10000 resource%R7 non-existing-node -10000;
--pcs -- constraint location add location-R7-another-one--INFINITY resource%R7 another-one -INFINITY;
--pcs -- constraint location add location-R7-localhost-INFINITY resource%R7 localhost INFINITY \
-+pcs -- constraint location add location-R7-non-existing-node--10000 resource%R7 non-existing-node score=-10000;
-+pcs -- constraint location add location-R7-another-one--INFINITY resource%R7 another-one score=-INFINITY;
-+pcs -- constraint location add location-R7-localhost-INFINITY resource%R7 localhost score=INFINITY \
-   resource-discovery=always;
--pcs -- constraint location add location-G2-localhost-INFINITY resource%G2 localhost INFINITY;
--pcs -- constraint location add location-R-localhost-INFINITY 'regexp%R*' localhost INFINITY;
-+pcs -- constraint location add location-G2-localhost-INFINITY resource%G2 localhost score=INFINITY;
-+pcs -- constraint location add location-R-localhost-INFINITY 'regexp%R*' localhost score=INFINITY;
- pcs -- constraint location resource%R6-clone rule \
-   id=loc_constr_with_not_expired_rule-rule constraint-id=loc_constr_with_not_expired_rule role=Unpromoted score=500 \
--  '#uname' eq node1 and date gt 2000-01-01;
--pcs -- constraint location resource%R6-clone rule  \
-+  '#uname eq node1 and date gt 2000-01-01';
-+pcs -- constraint location resource%R6-clone rule \
-   id=loc_constr_with_not_expired_rule-1-rule constraint-id=loc_constr_with_not_expired_rule-1 role=Promoted score-attribute=test-attr \
--  date gt 2010-12-31 and '#uname' eq node1;
--pcs -- constraint colocation add Promoted G1-clone with Stopped R6-clone -100 \
--  id=colocation-G1-clone-R6-clone--100;
-+  'date gt 2010-12-31 and #uname eq node1';
-+pcs -- constraint colocation add Promoted G1-clone with Stopped R6-clone \
-+  id=colocation-G1-clone-R6-clone--100 score=-100;
- pcs -- constraint colocation \
-   set R7 G2 role=Started \
-   set B2 R6-clone sequential=0 \
-@@ -22,9 +22,9 @@ pcs -- constraint order stop R7 then stop G2 \
- pcs -- constraint order start G2 then start B2 \
-   id=order-G2-B2-Optional kind=Optional;
- pcs -- constraint order \
--  set B2 R6-clone require-all=0 action=stop \
-+  set R6-clone B2 require-all=0 action=stop \
-   set G1-clone sequential=0 action=promote \
--  setoptions id=order_set_B2R6-cloneSe kind=Optional;
-+  setoptions id=order_set_R6-cloneB2Se kind=Optional;
- pcs -- constraint ticket add custom-ticket1 Promoted G1-clone \
-   id=ticket-custom-ticket1-G1-clone-Promoted loss-policy=demote;
- pcs -- constraint ticket \
-diff --git a/pcs_test/tier0/cli/constraint/output/__init__.py b/pcs_test/tier0/cli/constraint/output/__init__.py
-new file mode 100644
-index 000000000..e69de29bb
-diff --git a/pcs_test/tier0/cli/constraint/output/test_set.py b/pcs_test/tier0/cli/constraint/output/test_set.py
-new file mode 100644
-index 000000000..55031b066
---- /dev/null
-+++ b/pcs_test/tier0/cli/constraint/output/test_set.py
-@@ -0,0 +1,45 @@
-+from unittest import TestCase
-+
-+from pcs.cli.constraint.output.order import set_constraint_to_text
-+from pcs.common.pacemaker.constraint import (
-+    CibConstraintOrderAttributesDto,
-+    CibConstraintOrderSetDto,
-+)
-+from pcs.common.pacemaker.constraint.set import CibResourceSetDto
-+
-+
-+class TestSetConstraintToTextResourceOrder(TestCase):
-+    def test_resource_order_is_preserved(self):
-+        result = set_constraint_to_text(
-+            CibConstraintOrderSetDto(
-+                resource_sets=[
-+                    CibResourceSetDto(
-+                        set_id="set1",
-+                        sequential=None,
-+                        require_all=None,
-+                        ordering=None,
-+                        action=None,
-+                        role=None,
-+                        score=None,
-+                        kind=None,
-+                        resources_ids=["c_rsc", "a_rsc", "b_rsc"],
-+                    ),
-+                ],
-+                attributes=CibConstraintOrderAttributesDto(
-+                    constraint_id="order1",
-+                    symmetrical=None,
-+                    require_all=None,
-+                    score=None,
-+                    kind=None,
-+                ),
-+            ),
-+            with_id=False,
-+        )
-+        self.assertEqual(
-+            result,
-+            [
-+                "Set Constraint:",
-+                "  Resource Set:",
-+                "    Resources: 'c_rsc', 'a_rsc', 'b_rsc'",
-+            ],
-+        )
-diff --git a/pcs_test/tier0/cli/constraint/test_command.py b/pcs_test/tier0/cli/constraint/test_command.py
-index 10ef22a7b..3886be648 100644
---- a/pcs_test/tier0/cli/constraint/test_command.py
-+++ b/pcs_test/tier0/cli/constraint/test_command.py
-@@ -69,7 +69,7 @@ class TestRemoveConstraint(TestCase):
-             "loc_constr_with_expired_rule-rule",
-             "loc_constr_with_not_expired_rule-1-rule",
-             "colocation-G1-clone-R6-clone--100",
--            "order_set_B2R6-cloneSe",
-+            "order_set_R6-cloneB2Se",
-             "ticket_set_R7B2G2",
-         ]
-         self._call_cmd(constraint_or_rule_ids)
-diff --git a/pcs_test/tier1/constraint/test_config.py b/pcs_test/tier1/constraint/test_config.py
-index 9e177ffb9..0fade2857 100644
---- a/pcs_test/tier1/constraint/test_config.py
-+++ b/pcs_test/tier1/constraint/test_config.py
-@@ -336,7 +336,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint:
-                 score=-1
-                 Resource Set:
--                  Resources: 'G2', 'R7'
-+                  Resources: 'R7', 'G2'
-                   role=Started
-                 Resource Set:
-                   Resources: 'B2', 'R6-clone'
-@@ -350,7 +350,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint:
-                 kind=Optional
-                 Resource Set:
--                  Resources: 'B2', 'R6-clone'
-+                  Resources: 'R6-clone', 'B2'
-                   require-all=0 action=stop
-                 Resource Set:
-                   Resources: 'G1-clone'
-@@ -362,7 +362,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint:
-                 ticket=ticket2
-                 Resource Set:
--                  Resources: 'B2', 'G2', 'R7'
-+                  Resources: 'R7', 'B2', 'G2'
-                   role=Stopped
-             """
-         )
-@@ -404,7 +404,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint:
-                 score=-1
-                 Resource Set:
--                  Resources: 'G2', 'R7'
-+                  Resources: 'R7', 'G2'
-                   role=Started
-                 Resource Set:
-                   Resources: 'B2', 'R6-clone'
-@@ -418,7 +418,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint:
-                 kind=Optional
-                 Resource Set:
--                  Resources: 'B2', 'R6-clone'
-+                  Resources: 'R6-clone', 'B2'
-                   require-all=0 action=stop
-                 Resource Set:
-                   Resources: 'G1-clone'
-@@ -430,7 +430,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint:
-                 ticket=ticket2
-                 Resource Set:
--                  Resources: 'B2', 'G2', 'R7'
-+                  Resources: 'R7', 'B2', 'G2'
-                   role=Stopped
-             """
-         )
-@@ -468,7 +468,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint: colocation_set_R7G2B2
-                 score=-1
-                 Resource Set: colocation_set_R7G2B2_set
--                  Resources: 'G2', 'R7'
-+                  Resources: 'R7', 'G2'
-                   role=Started
-                 Resource Set: colocation_set_R7G2B2_set-1
-                   Resources: 'B2', 'R6-clone'
-@@ -479,12 +479,12 @@ class ConstraintConfigText(TestCase):
-               start resource 'G2' then start resource 'B2' (id: order-G2-B2-Optional)
-                 kind=Optional
-             Order Set Constraints:
--              Set Constraint: order_set_B2R6-cloneSe
-+              Set Constraint: order_set_R6-cloneB2Se
-                 kind=Optional
--                Resource Set: order_set_B2R6-cloneSe_set
--                  Resources: 'B2', 'R6-clone'
-+                Resource Set: order_set_R6-cloneB2Se_set
-+                  Resources: 'R6-clone', 'B2'
-                   require-all=0 action=stop
--                Resource Set: order_set_B2R6-cloneSe_set-1
-+                Resource Set: order_set_R6-cloneB2Se_set-1
-                   Resources: 'G1-clone'
-                   sequential=0 action=promote
-             Ticket Constraints:
-@@ -494,7 +494,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint: ticket_set_R7B2G2
-                 ticket=ticket2
-                 Resource Set: ticket_set_R7B2G2_set
--                  Resources: 'B2', 'G2', 'R7'
-+                  Resources: 'R7', 'B2', 'G2'
-                   role=Stopped
-             """
-         )
-@@ -536,7 +536,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint: colocation_set_R7G2B2
-                 score=-1
-                 Resource Set: colocation_set_R7G2B2_set
--                  Resources: 'G2', 'R7'
-+                  Resources: 'R7', 'G2'
-                   role=Started
-                 Resource Set: colocation_set_R7G2B2_set-1
-                   Resources: 'B2', 'R6-clone'
-@@ -547,12 +547,12 @@ class ConstraintConfigText(TestCase):
-               start resource 'G2' then start resource 'B2' (id: order-G2-B2-Optional)
-                 kind=Optional
-             Order Set Constraints:
--              Set Constraint: order_set_B2R6-cloneSe
-+              Set Constraint: order_set_R6-cloneB2Se
-                 kind=Optional
--                Resource Set: order_set_B2R6-cloneSe_set
--                  Resources: 'B2', 'R6-clone'
-+                Resource Set: order_set_R6-cloneB2Se_set
-+                  Resources: 'R6-clone', 'B2'
-                   require-all=0 action=stop
--                Resource Set: order_set_B2R6-cloneSe_set-1
-+                Resource Set: order_set_R6-cloneB2Se_set-1
-                   Resources: 'G1-clone'
-                   sequential=0 action=promote
-             Ticket Constraints:
-@@ -562,7 +562,7 @@ class ConstraintConfigText(TestCase):
-               Set Constraint: ticket_set_R7B2G2
-                 ticket=ticket2
-                 Resource Set: ticket_set_R7B2G2_set
--                  Resources: 'B2', 'G2', 'R7'
-+                  Resources: 'R7', 'B2', 'G2'
-                   role=Stopped
-             """
-         )
-diff --git a/pcs_test/tier1/legacy/test_constraints.py b/pcs_test/tier1/legacy/test_constraints.py
-index 3b5986a67..697a046a8 100644
---- a/pcs_test/tier1/legacy/test_constraints.py
-+++ b/pcs_test/tier1/legacy/test_constraints.py
-@@ -1892,13 +1892,13 @@ Error: invalid option 'foo', allowed options are: 'id', 'kind', 'symmetrical'
-                   Set Constraint: colocation_set_s1d1
-                     score=INFINITY
-                     Resource Set: colocation_set_s1d1_set
--                      Resources: 'dummy1', 'stateful1'
-+                      Resources: 'stateful1', 'dummy1'
-                 Order Constraints:
-                   start resource 'stateful1' then start resource 'dummy1' (id: order-stateful1-dummy1-mandatory)
-                 Order Set Constraints:
-                   Set Constraint: order_set_s1d1
-                     Resource Set: order_set_s1d1_set
--                      Resources: 'dummy1', 'stateful1'
-+                      Resources: 'stateful1', 'dummy1'
-                 """
-             ),
-         )
-@@ -2103,13 +2103,13 @@ Error: invalid option 'foo', allowed options are: 'id', 'kind', 'symmetrical'
-                   Set Constraint: colocation_set_d1dy
-                     score=INFINITY
-                     Resource Set: colocation_set_d1dy_set
--                      Resources: 'dummy', 'dummy1'
-+                      Resources: 'dummy1', 'dummy'
-                 Order Constraints:
-                   start resource 'dummy' then start resource 'dummy1' (id: order-dummy-dummy1-mandatory)
-                 Order Set Constraints:
-                   Set Constraint: order_set_d1dy
-                     Resource Set: order_set_d1dy_set
--                      Resources: 'dummy', 'dummy1'
-+                      Resources: 'dummy1', 'dummy'
-                 """
-             ),
-         )
-@@ -2901,7 +2901,7 @@ Error: duplicate constraint already exists, use --force to override
-                   Set Constraint: colocation_set_D6D1
-                     score=INFINITY
-                     Resource Set: colocation_set_D6D1_set
--                      Resources: 'D1', 'D6'
-+                      Resources: 'D6', 'D1'
-                 Order Set Constraints:
-                   Set Constraint: order_set_D1D2
-                     Resource Set: order_set_D1D2_set
-@@ -2921,7 +2921,7 @@ Error: duplicate constraint already exists, use --force to override
-                       Resources: 'D5', 'D6'
-                   Set Constraint: order_set_D6D1
-                     Resource Set: order_set_D6D1_set
--                      Resources: 'D1', 'D6'
-+                      Resources: 'D6', 'D1'
-                 """
-             ),
-         )
-@@ -3098,7 +3098,7 @@ Error: duplicate constraint already exists, use --force to override
-                   Set Constraint: id4
-                     score=100
-                     Resource Set: id4_set
--                      Resources: 'D1', 'D2'
-+                      Resources: 'D2', 'D1'
-                 Order Constraints:
-                   start resource 'D1' then start resource 'D2' (id: id7)
-                   start resource 'D2' then start resource 'D1' (id: id8)
-@@ -3110,7 +3110,7 @@ Error: duplicate constraint already exists, use --force to override
-                   Set Constraint: id6
-                     kind=Mandatory
-                     Resource Set: id6_set
--                      Resources: 'D1', 'D2'
-+                      Resources: 'D2', 'D1'
-                 """
-             ),
-         )
-diff --git a/pcs_test/tools/constraints_dto.py b/pcs_test/tools/constraints_dto.py
-index b8a7a5f01..5f2b6ff4d 100644
---- a/pcs_test/tools/constraints_dto.py
-+++ b/pcs_test/tools/constraints_dto.py
-@@ -371,7 +371,7 @@ def get_all_constraints(
-             CibConstraintOrderSetDto(
-                 resource_sets=[
-                     CibResourceSetDto(
--                        set_id="order_set_B2R6-cloneSe_set",
-+                        set_id="order_set_R6-cloneB2Se_set",
-                         sequential=None,
-                         require_all=False,
-                         ordering=None,
-@@ -379,10 +379,10 @@ def get_all_constraints(
-                         role=None,
-                         score=None,
-                         kind=None,
--                        resources_ids=["B2", "R6-clone"],
-+                        resources_ids=["R6-clone", "B2"],
-                     ),
-                     CibResourceSetDto(
--                        set_id="order_set_B2R6-cloneSe_set-1",
-+                        set_id="order_set_R6-cloneB2Se_set-1",
-                         sequential=False,
-                         require_all=None,
-                         ordering=None,
-@@ -394,7 +394,7 @@ def get_all_constraints(
-                     ),
-                 ],
-                 attributes=CibConstraintOrderAttributesDto(
--                    constraint_id="order_set_B2R6-cloneSe",
-+                    constraint_id="order_set_R6-cloneB2Se",
-                     symmetrical=None,
-                     require_all=None,
-                     score=None,
--- 
-2.54.0
-

diff --git a/drop-dependency-on-rubygem-cgi.patch b/drop-dependency-on-rubygem-cgi.patch
deleted file mode 100644
index 1656e58..0000000
--- a/drop-dependency-on-rubygem-cgi.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-From 8ffe54a16e9bfae829efd4706b0328477d98fa15 Mon Sep 17 00:00:00 2001
-From: Miroslav Lisik <mlisik@redhat.com>
-Date: Thu, 12 Feb 2026 18:41:24 +0100
-Subject: [PATCH 3/3] drop dependency on rubygem cgi
-
-* rubygem cgi was removed from default gems in Ruby-4.0.0
-  https://www.ruby-lang.org/en/news/2025/12/25/ruby-4-0-0-released/
-* there is no rubygem-cgi package in Fedora
-* replace CGI::Cookie.to_s method with Rack::Utils.set_cookie_header
-  function
----
- pcsd/pcs.rb  | 4 ++--
- pcsd/pcsd.rb | 1 -
- 2 files changed, 2 insertions(+), 3 deletions(-)
-
-diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb
-index 924a3dda1..86be4db0c 100644
---- a/pcsd/pcs.rb
-+++ b/pcsd/pcs.rb
-@@ -3,7 +3,7 @@
- require 'etc'
- require 'childprocess'
- require 'shellwords'
--require 'cgi'
-+require 'rack'
- require 'net/http'
- require 'net/https'
- require 'uri'
-@@ -431,7 +431,7 @@ def _get_cookie_list(auth_user, cookies_data)
- 
-   cookies_data_default.update(cookies_data)
-   cookies_data_default.each { |name, value|
--    cookie_list << CGI::Cookie.new('name' => name, 'value' => value).to_s
-+    cookie_list << Rack::Utils.set_cookie_header(name, value)
-   }
-   return cookie_list
- end
-diff --git a/pcsd/pcsd.rb b/pcsd/pcsd.rb
-index 181945b9d..2a515f70c 100644
---- a/pcsd/pcsd.rb
-+++ b/pcsd/pcsd.rb
-@@ -4,7 +4,6 @@ require 'openssl'
- require 'logger'
- require 'thread'
- require 'fileutils'
--require 'cgi'
- 
- require 'bootstrap.rb'
- require 'resource.rb'
--- 
-2.53.0
-

diff --git a/pcs.spec b/pcs.spec
index 91e3b12..bfaafa6 100644
--- a/pcs.spec
+++ b/pcs.spec
@@ -1,6 +1,6 @@
 Name: pcs
-Version: 0.12.2
-Release: 4%{?dist}
+Version: 0.12.3
+Release: 1%{?dist}
 # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/
 # https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses
 # GPL-2.0-only: pcs
@@ -16,7 +16,7 @@ BuildArch: noarch
 
 # To build an official pcs release, comment out branch_or_commit
 # Use long commit hash or branch name to build an unreleased version
-# %%global branch_or_commit 1353dfbb3af82d77f4de17a3fa4cbde185bb2b2d
+%global branch_or_commit 0.12.3.1
 %global version_or_commit %{clean_version}
 %if 0%{?branch_or_commit:1}
   %global version_or_commit %{branch_or_commit}
@@ -26,8 +26,8 @@ BuildArch: noarch
 
 # To build an official pcs-web-ui release, comment out ui_branch_or_commit
 # Last tagged version, also used as fallback version for untagged tarballs
-%global ui_version 0.1.24.3
-%global ui_modules_version 0.1.24.3
+%global ui_version 0.1.25
+%global ui_modules_version 0.1.25
 # Use long commit hash or branch name to build an unreleased version
 # %%global ui_branch_or_commit 34372d1268f065ed186546f55216aaa2d7e76b54
 %global ui_version_or_commit %{ui_version}
@@ -73,10 +73,7 @@ Source101: https://github.com/ClusterLabs/pcs-web-ui/releases/download/%{ui_vers
 # pcs patches: <= 200
 # Patch1: name.patch
 Patch1: show-info-page-instead-of-webui.patch
-Patch2: drop-dependency-on-rubygem-cgi.patch
-Patch3: typing-fixes-for-python-3.15.patch
-Patch4: bz2458608-01-fix-a-crash-when-determining-terminal-size.patch
-Patch5: bz2461143-01-Update-constraint-order-printout.patch
+Patch2: swap-rubygem-ethon-for-curb.patch
 
 # ui patches: >200
 # Patch201: name-web-ui.patch
@@ -119,8 +116,7 @@ BuildRequires: ruby >= 2.5.0
 BuildRequires: ruby-devel
 BuildRequires: rubygem(backports)
 BuildRequires: rubygem(childprocess)
-BuildRequires: rubygem(ethon)
-BuildRequires: rubygem(ffi)
+BuildRequires: rubygem(curb)
 BuildRequires: rubygem(json)
 BuildRequires: rubygem(logger)
 BuildRequires: rubygem(mustermann)
@@ -173,8 +169,7 @@ Requires: python3-tornado
 Requires: ruby >= 3.3.0
 Requires: rubygem(backports)
 Requires: rubygem(childprocess)
-Requires: rubygem(ethon)
-Requires: rubygem(ffi)
+Requires: rubygem(curb)
 Requires: rubygem(json)
 Requires: rubygem(logger)
 Requires: rubygem(mustermann)
@@ -552,6 +547,11 @@ fi
 
 
 %changelog
+* Fri Sep 11 2026 Michal Pospíšil <mpospisi@redhat.com> - 0.12.3-1
+- Rebased pcs to the newest major version (see CHANGELOG.md)
+- Updated standalone web UI and HA Cluster Management Cockpit application to pcs-web-ui 0.1.25 (see CHANGELOG_WUI.md)
+- pcs no longer depends on rubygems ethon and ffi, rubygem curb is used instead
+
 * Thu Jul 16 2026 Fedora Release Engineering <releng@fedoraproject.org> - 0.12.2-4
 - Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild
 

diff --git a/show-info-page-instead-of-webui.patch b/show-info-page-instead-of-webui.patch
index 95c3a24..8bb838c 100644
--- a/show-info-page-instead-of-webui.patch
+++ b/show-info-page-instead-of-webui.patch
@@ -1,25 +1,25 @@
-From f1137799d8fdb5293243a4b106e6550b99aa6477 Mon Sep 17 00:00:00 2001
+From 6e0fbf2b89cb322e66941929409af3836bc7aeac Mon Sep 17 00:00:00 2001
 From: Ivan Devat <idevat@redhat.com>
 Date: Tue, 5 Nov 2024 16:35:02 +0100
-Subject: [PATCH 1/3] show info page instead of webui
+Subject: [PATCH 1/2] show info page instead of webui
 
 ---
  pcs/Makefile.am                      |  1 +
  pcs/daemon/app/webui_info_handler.py | 31 ++++++++++++++++++++++++++++
- pcs/daemon/run.py                    |  4 +++-
+ pcs/daemon/run.py                    |  6 ++++++
  pcs_test/smoke.sh.in                 |  4 ++--
  pcsd/public/ui_instructions.html     | 24 ++++++++++-----------
- 5 files changed, 48 insertions(+), 16 deletions(-)
+ 5 files changed, 51 insertions(+), 15 deletions(-)
  create mode 100644 pcs/daemon/app/webui_info_handler.py
 
 diff --git a/pcs/Makefile.am b/pcs/Makefile.am
-index c427441de..5e67457d9 100644
+index d70da2895..eca1622e7 100644
 --- a/pcs/Makefile.am
 +++ b/pcs/Makefile.am
-@@ -226,6 +226,7 @@ EXTRA_DIST		= \
+@@ -240,6 +240,7 @@ EXTRA_DIST		= \
+ 			  daemon/app/webui/auth_provider.py \
  			  daemon/app/webui/core.py \
  			  daemon/app/webui/session.py \
- 			  daemon/app/webui/sinatra_ui.py \
 +			  daemon/app/webui_info_handler.py \
  			  daemon/async_tasks/__init__.py \
  			  daemon/async_tasks/scheduler.py \
@@ -62,27 +62,29 @@ index 000000000..3ab8275bf
 +        (r"/ui/.*", _WebuiInfoHandler, dict(path=path)),
 +    ]
 diff --git a/pcs/daemon/run.py b/pcs/daemon/run.py
-index 87d526d50..ef9e90f5a 100644
+index bb53d979b..b3317e959 100644
 --- a/pcs/daemon/run.py
 +++ b/pcs/daemon/run.py
-@@ -33,6 +33,7 @@ from pcs.daemon.app import (
-     auth,
+@@ -34,6 +34,7 @@ from pcs.daemon.app import (
      sinatra_remote,
      sinatra_ui,
+     ui_manage,
 +    webui_info_handler,
  )
  from pcs.daemon.app import capabilities as capabilities_app
  
-@@ -168,7 +169,8 @@ def configure_app(  # noqa: PLR0913
-             # Even with disabled (standalone) webui the following routes must be
-             # provided because they can be used via unix socket from cockpit.
-             routes.extend(
--                sinatra_ui.get_routes(auth_provider, ruby_pcsd_wrapper)
-+                webui_info_handler.get_routes(webui_fallback)
-+                + sinatra_ui.get_routes(auth_provider, ruby_pcsd_wrapper)
+@@ -195,6 +196,11 @@ def configure_app(  # noqa: PLR0913
+                     auth_provider=lib_auth_provider,
+                 )
              )
- 
-         return Application(
++        else:
++            routes.extend(
++                webui_info_handler.get_routes(webui_fallback)
++            )
++
+         # Even with disabled (standalone) webui the following routes must be
+         # provided because they can be used via unix socket from cockpit.
+         # Handlers for these routes are the same in both cases, the only
 diff --git a/pcs_test/smoke.sh.in b/pcs_test/smoke.sh.in
 index fdfe8be2c..a9bb83447 100755
 --- a/pcs_test/smoke.sh.in
@@ -144,5 +146,5 @@ index a120ed3d4..b9c1c3152 100644
  </body>
  </html>
 -- 
-2.53.0
+2.55.0
 

diff --git a/sources b/sources
index fe26f00..9c8aab4 100644
--- a/sources
+++ b/sources
@@ -1,5 +1,5 @@
 SHA512 (pyagentx-0.4.pcs.2.tar.gz) = d4194fec9a3e5fefe3793d49b7fec1feafef294c7e613a06046c2993daeefc5cb39d7c5b2b402ff83e49b2d976953f862264288c758c0be09d997b5323cc558a
 SHA512 (dacite-1.9.2.tar.gz) = 69513bc1acd43e45a208ce4b4186072f92d9057a6a88c77d56f21405fbed7964d94fe4f6a37c99754a793caa2bd0584664dfab69169ef0d3564bb09d37769e59
-SHA512 (pcs-0.12.2.tar.gz) = dacefc4acf7a5857606ddbe4a9dcb539af6288740f564ee565d25bc05cfa9f5a0d72e45262548d8d5c20a4facb5c14c2c70e859f0d4c091acae45e962b99e6ea
-SHA512 (pcs-web-ui-0.1.24.3.tar.gz) = 82ceca8528982d29d4f231aaef1beb224e416877f78a150bdf979c59d41c7b4071b0e7b300eb22c4747ca8b6829fe44f5f4a7e13162418614c1e2a5102439cde
-SHA512 (pcs-web-ui-node-modules-0.1.24.3.tar.xz) = f5ebdda26371906eb4e8673b58dbc639492f29fdbdeca5f76b1988ac42fb53e828753204268916cef87441905c66d39e4d30acd6069707cc39b25b84014ef283
+SHA512 (pcs-0.12.3.1.tar.gz) = 51a32d7e4c56422319943eee2d40c88ed8030e86c81e91326ca7c29a96fe898c60728fa8ffedb86b4cfabe2bafb4170b2e9cb12c1a0ea3b0fa3f93873255e92e
+SHA512 (pcs-web-ui-0.1.25.tar.gz) = 8380e17743b47099e1fa941d2099a162c10bbc9cbb4103d21c340f0f0385d26550aced5e14dd180deacb4102678ea5f5a5e990e3156b6c087b2ff4aeaed5f41b
+SHA512 (pcs-web-ui-node-modules-0.1.25.tar.xz) = 56003da4a14db540ebc939bbcd13ac44a9b6a75d064f0ba6267368a4cbebc58382fc008fd1ce85624f97c01f7020dafbbed3333c009ce347f0ee919a41d8a6c4

diff --git a/swap-rubygem-ethon-for-curb.patch b/swap-rubygem-ethon-for-curb.patch
new file mode 100644
index 0000000..77f3b79
--- /dev/null
+++ b/swap-rubygem-ethon-for-curb.patch
@@ -0,0 +1,159 @@
+From 9d8f40c5a05046186972b9224d19e71f9a5e72c0 Mon Sep 17 00:00:00 2001
+From: Miroslav Lisik <mlisik@redhat.com>
+Date: Wed, 1 Jul 2026 10:57:42 +0200
+Subject: [PATCH 2/2] pcsd-ruby: swap rubygem-ethon for rubygem-curb
+
+---
+ README.md       |  2 +-
+ configure.ac    |  3 +--
+ pcsd/pcs.rb     | 41 ++++++++++++++++++++---------------------
+ rpm/pcs.spec.in | 12 +++++-------
+ 4 files changed, 27 insertions(+), 31 deletions(-)
+
+diff --git a/README.md b/README.md
+index 636485be6..eb6ea49d0 100644
+--- a/README.md
++++ b/README.md
+@@ -56,7 +56,7 @@ installation:
+ * pacemaker-libs-devel (or libpacemaker-devel and libpacemaker3-devel)
+ * gcc
+ * gcc-c++
+-* FFI development files (package libffi-devel or libffi-dev)
++* libcurl development files (package libcurl-devel or libcurl-dev)
+ * printf (package coreutils)
+ * redhat-rpm-config (if you are using Fedora)
+ * wget (to download bundled libraries)
+diff --git a/configure.ac b/configure.ac
+index 7c2ee0f73..0393a86a1 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -482,8 +482,7 @@ REQUIRE_RACKUP="require 'rackup'"
+ if test "x$tests_only" != "xyes"; then
+ 	PCS_CHECK_GEM([backports])
+ 	PCS_CHECK_GEM([childprocess])
+-	PCS_CHECK_GEM([ethon])
+-	PCS_CHECK_GEM([ffi])
++	PCS_CHECK_GEM([curb])
+ 	PCS_CHECK_GEM([json])
+ 	PCS_CHECK_GEM([ruby2_keywords])
+ 	PCS_CHECK_GEM([mustermann])
+diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb
+index 32373f344..fcbf93ecf 100644
+--- a/pcsd/pcs.rb
++++ b/pcsd/pcs.rb
+@@ -11,7 +11,7 @@ require 'json'
+ require 'fileutils'
+ require 'backports/latest'
+ require 'base64'
+-require 'ethon'
++require 'curb'
+ require 'openssl'
+ require 'stringio'
+ 
+@@ -349,32 +349,31 @@ def send_request(
+ 
+   $logger.info "Connecting to: #{url}"
+ 
+-  req = Ethon::Easy.new()
+-  req.set_attributes({
+-    :url => url,
+-    :timeout_ms => timeout_ms,
+-    :cookie => _get_cookie_list(auth_user, cookies_data).join(';'),
+-    :ssl_verifyhost => 0,
+-    :ssl_verifypeer => 0,
+-    :postfields => (encoded_data) ? encoded_data : nil,
+-    :httpget => (post ? 0 : 1),
+-    :nosignal => 1, # required for multi-threading
+-  })
+-  req.compose_header('Expect', '')
+-  return_code = req.perform
+-  response_code = req.response_code
+-  response_body = req.response_body
+-  req.cleanup
+-  if return_code == :ok
+-    return response_code, response_body
+-  else
++  begin
++    curl = Curl::Easy.new(url)
++    curl.timeout_ms = timeout_ms
++    curl.cookies = _get_cookie_list(auth_user, cookies_data).join(';')
++    curl.ssl_verify_host = false
++    curl.ssl_verify_peer = false
++    curl.nosignal = true # required for multi-threading
++    curl.headers['Expect'] = ''
++
++    if post
++      curl.post_body = encoded_data
++      curl.http_post
++    else
++      curl.http_get
++    end
++
++    return curl.response_code, curl.body_str
++  rescue Curl::Err::CurlError => e
+     if is_proxy_set(ENV)
+       $logger.warn(
+         'Proxy is set in environment variables, try disabling it'
+       )
+     end
+     $logger.info(
+-      "No response from: #{node} request: #{request}, error: #{return_code}"
++      "No response from: #{node} request: #{request}, error: #{e.message}"
+     )
+     return 400,'{"noresponse":true}'
+   end
+diff --git a/rpm/pcs.spec.in b/rpm/pcs.spec.in
+index 0267bfb58..9ab3df335 100644
+--- a/rpm/pcs.spec.in
++++ b/rpm/pcs.spec.in
+@@ -16,18 +16,16 @@ Release: 99+git%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty
+ # GPL-2.0-only: pcs
+ # Apache-2.0: dataclasses, tornado
+ # Apache-2.0 OR BSD-3-Clause: dateutil
+-# MIT: backports, childprocess, dacite, ethon, mustermann, rack, rackup,
++# MIT: backports, childprocess, dacite, mustermann, rack, rackup,
+ #      rack-protection, rack-session, rack-test, sinatra, tilt
+ # MIT AND (BSD-2-Clause OR GPL-2.0-or-later): nio4r
+ # BSD-2-Clause: rexml
+ # BSD-2-Clause or Ruby: base64, ruby2_keywords, strscan
+ # BSD 3-Clause: puma
+-# BSD-3-Clause and MIT: ffi
++# Ruby: curb
+ # curl OR LGPL-2.1-or-later: python-pycurl
+ # Some gems we bundle are just dependencies of gems that we use,
+ # here is their dependency tree (we directly require the 1st level only):
+-# ethon:
+-#   ffi
+ # puma:
+ #   nio4r
+ # sinatra:
+@@ -43,7 +41,7 @@ Release: 99+git%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty
+ #   base64
+ # rexml:
+ #   strscan (might be included with Ruby)
+-License: GPL-2.0-only AND Apache-2.0 AND MIT AND BSD-2-Clause AND BSD-3-Clause AND (BSD-2-Clause OR GPL-2.0-or-later) AND (Apache-2.0 OR BSD-3-Clause) AND (BSD-2-Clause OR Ruby) AND (curl OR LGPL-2.1-or-later)
++License: GPL-2.0-only AND Apache-2.0 AND MIT AND BSD-2-Clause AND BSD-3-Clause AND Ruby AND (BSD-2-Clause OR GPL-2.0-or-later) AND (Apache-2.0 OR BSD-3-Clause) AND (BSD-2-Clause OR Ruby) AND (curl OR LGPL-2.1-or-later)
+ URL: https://github.com/ClusterLabs/pcs
+ Summary: Pacemaker/Corosync Configuration System
+ 
+@@ -101,7 +99,8 @@ BuildRequires: python%{python3_version}-pyparsing >= 3.0.0
+ # required to pass ./configure
+ BuildRequires: python%{python3_version}-lxml
+ 
+-# pycurl is not distributed in rhel 10
++# pycurl is not distributed in rhel 10, libcurl-devel is also needed for
++# compiling bundled rubygem-curb native extension
+ %if 0%{?rhel} >= 10
+ BuildRequires: libcurl-devel
+ BuildRequires: openssl-devel
+@@ -124,7 +123,6 @@ BuildRequires: findutils
+ # gcc for compiling custom rubygems
+ BuildRequires: gcc
+ BuildRequires: gcc-c++
+-BuildRequires: libffi-devel
+ # ruby and gems for pcsd
+ BuildRequires: ruby >= 3.1
+ BuildRequires: ruby-devel
+-- 
+2.55.0
+

diff --git a/typing-fixes-for-python-3.15.patch b/typing-fixes-for-python-3.15.patch
deleted file mode 100644
index 7f32424..0000000
--- a/typing-fixes-for-python-3.15.patch
+++ /dev/null
@@ -1,601 +0,0 @@
-From 5b6651865a233c2a73c7bddb410898334c171cf7 Mon Sep 17 00:00:00 2001
-From: Tomas Jelinek <tojeline@redhat.com>
-Date: Mon, 2 Mar 2026 14:39:08 +0100
-Subject: [PATCH 2/3] typing fixes for python 3.15
-
----
- pcs/cli/cluster_property/output.py            |  19 +-
- pcs/common/interface/dto.py                   | 179 ++++++++++++++----
- pcs/common/pacemaker/cluster_property.py      |   3 +-
- pcs/common/pacemaker/constraint/set.py        |   8 +-
- pcs/common/pacemaker/resource/group.py        |   8 +-
- pcs/common/pacemaker/resource/relations.py    |  13 +-
- pcs/common/pacemaker/tag.py                   |   3 +-
- pcs/lib/permissions/config/types.py           |   8 +-
- pcs_test/tier0/common/interface/test_dto.py   |  22 +--
- .../lib/permissions/config/test_parser.py     |  46 ++---
- 10 files changed, 179 insertions(+), 130 deletions(-)
-
-diff --git a/pcs/cli/cluster_property/output.py b/pcs/cli/cluster_property/output.py
-index c9c46d1c3..68c6fc2d6 100644
---- a/pcs/cli/cluster_property/output.py
-+++ b/pcs/cli/cluster_property/output.py
-@@ -1,26 +1,17 @@
- from shlex import quote
--from typing import (
--    Optional,
--    Sequence,
--)
-+from typing import Optional, Sequence
- 
- from pcs.cli.nvset import nvset_dto_to_lines
- from pcs.cli.resource.output import resource_agent_parameter_metadata_to_text
- from pcs.common.pacemaker.cluster_property import ClusterPropertyMetadataDto
--from pcs.common.pacemaker.nvset import (
--    CibNvsetDto,
--    ListCibNvsetDto,
--)
-+from pcs.common.pacemaker.nvset import CibNvsetDto, ListCibNvsetDto
- from pcs.common.resource_agent.dto import ResourceAgentParameterDto
- from pcs.common.str_tools import (
-     format_name_value_default_list,
-     format_name_value_list,
-     indent,
- )
--from pcs.common.types import (
--    StringCollection,
--    StringSequence,
--)
-+from pcs.common.types import StringCollection, StringSequence
- 
- 
- class PropertyConfigurationFacade:
-@@ -35,7 +26,7 @@ class PropertyConfigurationFacade:
-             self._properties[0].nvpairs if self._properties else []
-         )
-         self._properties_metadata = properties_metadata
--        self._readonly_properties = readonly_properties
-+        self._readonly_properties = list(readonly_properties)
-         self._defaults_map = self.get_defaults(include_advanced=True)
-         self._name_nvpair_dto_map = {
-             nvpair_dto.name: nvpair_dto for nvpair_dto in self._first_nvpair_set
-@@ -82,7 +73,7 @@ class PropertyConfigurationFacade:
-         return self._properties_metadata
- 
-     @property
--    def readonly_properties(self) -> StringCollection:
-+    def readonly_properties(self) -> list[str]:
-         return self._readonly_properties
- 
-     def get_property_value(
-diff --git a/pcs/common/interface/dto.py b/pcs/common/interface/dto.py
-index c504bb5c7..a7efdfb74 100644
---- a/pcs/common/interface/dto.py
-+++ b/pcs/common/interface/dto.py
-@@ -1,18 +1,17 @@
--from dataclasses import (
--    asdict,
--    fields,
--    is_dataclass,
--)
--from enum import Enum
-+from dataclasses import asdict, fields, is_dataclass
-+from enum import Enum, EnumType
-+from types import NoneType, UnionType
- from typing import (
-     TYPE_CHECKING,
-     Any,
--    Dict,
-     Iterable,
-     NewType,
-     TypeVar,
-     Union,
-+    get_type_hints,
- )
-+from typing import get_args as get_type_args
-+from typing import get_origin as get_type_origin
- 
- import dacite
- 
-@@ -29,11 +28,9 @@ else:
- 
- 
- PrimitiveType = Union[str, int, float, bool, None]
--DtoPayload = Dict[str, "SerializableType"]  # type: ignore
--SerializableType = Union[  # type: ignore
--    PrimitiveType,
--    DtoPayload,  # type: ignore
--    Iterable["SerializableType"],  # type: ignore
-+DtoPayload = dict[str, "SerializableType"]
-+SerializableType = Union[
-+    PrimitiveType, DtoPayload, Iterable["SerializableType"]
- ]
- 
- T = TypeVar("T")
-@@ -46,20 +43,62 @@ class PayloadConversionError(Exception):
-     pass
- 
- 
-+class _UnionNotAllowed(Exception):
-+    pass
-+
-+
- class DataTransferObject(DataclassInstance):
-     pass
- 
- 
--def meta(name: str) -> Dict[str, str]:
--    metadata: Dict[str, str] = {}
-+def meta(name: str) -> dict[str, str]:
-+    metadata: dict[str, str] = {}
-     if name:
-         metadata[META_NAME] = name
-     return metadata
- 
- 
--# _type is Any, since based on static code analysis it can be either of
--# type[Any], str, None - depending on the step in dataclass instance
--# initialization
-+# _type is Any - in reality, it is either one of:
-+# * type
-+# * enum.EnumType
-+# * something defined in typing module, e.g. typing._GenericAlias, typing.Union
-+# Especially the typing module changes with new Python versions.
-+# Properly typing (rather metatyping, since its input and output are types)
-+# this function doesn't bring any benefits.
-+def _extract_type_from_optional(_type: Any) -> Any:
-+    # Dataclass fields may be typed as 'Optional[some_type]' or
-+    # 'Union[some_type, None]' or 'some_type | None'. This function extracts
-+    # the inner type from an Optional, and thus allows to properly detect types
-+    # of such dataclass fields. It raises an exception if a Union contains more
-+    # than one type other than None, because in that case it is unclear which
-+    # one is the correct type. However, such a field should never be defined in
-+    # a dataclass, because field type must be unambiguous.
-+
-+    # Internal representation of Union and Optional is different in Python 3.12
-+    # and 3.14. To be able to handle the differences, typing.get_origin is
-+    # used. It transforms all the representations to Union or UnionType.
-+    # https://docs.python.org/3/library/typing.html#typing.Union
-+    _type_origin = get_type_origin(_type)
-+    if not (_type_origin is Union or _type_origin is UnionType):
-+        return _type
-+
-+    inner_types_without_none = [
-+        inner_type
-+        for inner_type in get_type_args(_type)
-+        if inner_type is not NoneType
-+    ]
-+    if len(inner_types_without_none) == 1:
-+        return inner_types_without_none[0]
-+    raise _UnionNotAllowed()
-+
-+
-+# _type is Any - in reality, it is either one of:
-+# * type
-+# * enum.EnumType
-+# * something defined in typing module, e.g. typing._GenericAlias, typing.Union
-+# Especially the typing module changes with new Python versions.
-+# Properly typing (rather metatyping, since its input and output are types)
-+# this function doesn't bring any benefits.
- def _is_compatible_type(_type: Any, arg_index: int) -> bool:
-     return (
-         hasattr(_type, "__args__")
-@@ -68,29 +107,68 @@ def _is_compatible_type(_type: Any, arg_index: int) -> bool:
-     )
- 
- 
-+# _type is Any - in reality, it is either one of:
-+# * type
-+# * enum.EnumType
-+# * something defined in typing module, e.g. typing._GenericAlias, typing.Union
-+# Especially the typing module changes with new Python versions.
-+# Properly typing (rather metatyping, since its input and output are types)
-+# this function doesn't bring any benefits.
-+def _is_enum_type(_type: Any, arg_index: int) -> bool:
-+    return (
-+        hasattr(_type, "__args__")
-+        and len(_type.__args__) >= arg_index
-+        and type(_type.__args__[arg_index]) is EnumType
-+    )
-+
-+
-+# returns Any as the type of enum value can be anything and it can be different
-+# for each Enum
-+def _convert_enum(value: Enum) -> Any:
-+    return value.value
-+
-+
- def _convert_dict(
-     klass: type[DataTransferObject], obj_dict: DtoPayload
- ) -> DtoPayload:
-     new_dict = {}
-+    # resolve forward references in type hints, because type-detecting
-+    # functions do not work with forward references
-+    type_hints = get_type_hints(klass)
-     for _field in fields(klass):
-+        try:
-+            _type = _extract_type_from_optional(type_hints[_field.name])
-+        except _UnionNotAllowed as e:
-+            raise AssertionError(
-+                f"Field '{_field.name}' in class '{klass}' is a Union: "
-+                f"{_field.type}. "
-+                "Dataclass fields cannot be Unions, unless they are a Union of "
-+                "one type and None (which is equal to Optional)."
-+            ) from e
-         value = obj_dict[_field.name]
--        if is_dataclass(_field.type):
--            value = _convert_dict(_field.type, value)  # type: ignore
--        elif isinstance(value, list) and _is_compatible_type(_field.type, 0):
--            value = [
--                # ignore _field.type may not have __args__
--                # this is prevented by _is_compatible_type
--                _convert_dict(_field.type.__args__[0], item)  # type: ignore[union-attr]
--                for item in value
-+
-+        new_value: SerializableType
-+        if value is None:
-+            # None must be handled here, other checks fail if they get None
-+            new_value = value
-+        elif is_dataclass(_type):
-+            new_value = _convert_dict(_type, value)  # type: ignore
-+        elif isinstance(value, list) and _is_compatible_type(_type, 0):
-+            new_value = [
-+                _convert_dict(_type.__args__[0], item) for item in value
-             ]
--        elif isinstance(value, dict) and _is_compatible_type(_field.type, 1):
--            value = {
--                item_key: _convert_dict(_field.type.__args__[1], item_val)  # type: ignore[union-attr,arg-type]
-+        elif isinstance(value, list) and _is_enum_type(_type, 0):
-+            new_value = [_convert_enum(item) for item in value]
-+        elif isinstance(value, dict) and _is_compatible_type(_type, 1):
-+            new_value = {
-+                item_key: _convert_dict(_type.__args__[1], item_val)  # type: ignore[arg-type]
-                 for item_key, item_val in value.items()
-             }
-         elif isinstance(value, Enum):
--            value = value.value
--        new_dict[_field.metadata.get(META_NAME, _field.name)] = value
-+            new_value = _convert_enum(value)
-+        else:
-+            new_value = value
-+        new_dict[_field.metadata.get(META_NAME, _field.name)] = new_value
-     return new_dict
- 
- 
-@@ -106,27 +184,44 @@ def _convert_payload(klass: type[DTOTYPE], data: DtoPayload) -> DtoPayload:
-         new_dict = dict(data)
-     except ValueError as e:
-         raise PayloadConversionError() from e
-+    # resolve forward references in type hints, because type-detecting
-+    # functions do not work with forward references
-+    type_hints = get_type_hints(klass)
-     for _field in fields(klass):
-         new_name = _field.metadata.get(META_NAME, _field.name)
-         if new_name not in data:
-             continue
-+
-+        try:
-+            _type = _extract_type_from_optional(type_hints[_field.name])
-+        except _UnionNotAllowed as e:
-+            raise AssertionError(
-+                f"Field '{_field.name}' in class '{klass}' is a Union: "
-+                f"{_field.type}. "
-+                "Dataclass fields cannot be Unions, unless they are a Union of "
-+                "one type and None (which is equal to Optional)."
-+            ) from e
-         value = data[new_name]
--        if is_dataclass(_field.type):
--            value = _convert_payload(_field.type, value)  # type: ignore
--        elif isinstance(value, list) and _is_compatible_type(_field.type, 0):
--            value = [
--                # ignore _field.type may not have __args__
--                # this is prevented by _is_compatible_type
--                _convert_payload(_field.type.__args__[0], item)  # type: ignore
--                for item in value
-+
-+        new_value: SerializableType
-+        if value is None:
-+            # None must be handled here, other checks fail if they get None
-+            new_value = value
-+        elif is_dataclass(_type):
-+            new_value = _convert_payload(_type, value)  # type: ignore
-+        elif isinstance(value, list) and _is_compatible_type(_type, 0):
-+            new_value = [
-+                _convert_payload(_type.__args__[0], item) for item in value
-             ]
--        elif isinstance(value, dict) and _is_compatible_type(_field.type, 1):
--            value = {
--                item_key: _convert_payload(_field.type.__args__[1], item_val)  # type: ignore[union-attr,arg-type]
-+        elif isinstance(value, dict) and _is_compatible_type(_type, 1):
-+            new_value = {
-+                item_key: _convert_payload(_type.__args__[1], item_val)  # type: ignore[arg-type]
-                 for item_key, item_val in value.items()
-             }
-+        else:
-+            new_value = value
-         del new_dict[new_name]
--        new_dict[_field.name] = value
-+        new_dict[_field.name] = new_value
-     return new_dict
- 
- 
-diff --git a/pcs/common/pacemaker/cluster_property.py b/pcs/common/pacemaker/cluster_property.py
-index 06cfd423c..f6a92cb6e 100644
---- a/pcs/common/pacemaker/cluster_property.py
-+++ b/pcs/common/pacemaker/cluster_property.py
-@@ -3,10 +3,9 @@ from typing import Sequence
- 
- from pcs.common.interface.dto import DataTransferObject
- from pcs.common.resource_agent.dto import ResourceAgentParameterDto
--from pcs.common.types import StringCollection
- 
- 
- @dataclass(frozen=True)
- class ClusterPropertyMetadataDto(DataTransferObject):
-     properties_metadata: Sequence[ResourceAgentParameterDto]
--    readonly_properties: StringCollection
-+    readonly_properties: list[str]
-diff --git a/pcs/common/pacemaker/constraint/set.py b/pcs/common/pacemaker/constraint/set.py
-index e32839ec9..f46899888 100644
---- a/pcs/common/pacemaker/constraint/set.py
-+++ b/pcs/common/pacemaker/constraint/set.py
-@@ -1,16 +1,12 @@
- from dataclasses import dataclass
- from typing import Optional
- 
--from pcs.common.const import (
--    PcmkAction,
--    PcmkRoleType,
--)
-+from pcs.common.const import PcmkAction, PcmkRoleType
- from pcs.common.interface.dto import DataTransferObject
- from pcs.common.pacemaker.types import (
-     CibResourceSetOrdering,
-     CibResourceSetOrderType,
- )
--from pcs.common.types import StringSequence
- 
- 
- @dataclass(frozen=True)
-@@ -24,4 +20,4 @@ class CibResourceSetDto(DataTransferObject):
-     role: Optional[PcmkRoleType]
-     score: Optional[str]
-     kind: Optional[CibResourceSetOrderType]
--    resources_ids: StringSequence
-+    resources_ids: list[str]
-diff --git a/pcs/common/pacemaker/resource/group.py b/pcs/common/pacemaker/resource/group.py
-index 0895449db..0d7649ff0 100644
---- a/pcs/common/pacemaker/resource/group.py
-+++ b/pcs/common/pacemaker/resource/group.py
-@@ -1,18 +1,14 @@
- from dataclasses import dataclass
--from typing import (
--    Optional,
--    Sequence,
--)
-+from typing import Optional, Sequence
- 
- from pcs.common.interface.dto import DataTransferObject
- from pcs.common.pacemaker.nvset import CibNvsetDto
--from pcs.common.types import StringSequence
- 
- 
- @dataclass(frozen=True)
- class CibResourceGroupDto(DataTransferObject):
-     id: str  # pylint: disable=invalid-name
-     description: Optional[str]
--    member_ids: StringSequence
-+    member_ids: list[str]
-     meta_attributes: Sequence[CibNvsetDto]
-     instance_attributes: Sequence[CibNvsetDto]
-diff --git a/pcs/common/pacemaker/resource/relations.py b/pcs/common/pacemaker/resource/relations.py
-index d46eaed6c..2b4e76aae 100644
---- a/pcs/common/pacemaker/resource/relations.py
-+++ b/pcs/common/pacemaker/resource/relations.py
-@@ -1,22 +1,15 @@
- from dataclasses import dataclass
--from typing import (
--    Any,
--    Mapping,
--    Sequence,
--)
-+from typing import Any, Mapping, Sequence
- 
- from pcs.common.interface.dto import DataTransferObject
--from pcs.common.types import (
--    ResourceRelationType,
--    StringSequence,
--)
-+from pcs.common.types import ResourceRelationType
- 
- 
- @dataclass(frozen=True)
- class RelationEntityDto(DataTransferObject):
-     id: str  # pylint: disable=invalid-name
-     type: ResourceRelationType
--    members: StringSequence
-+    members: list[str]
-     metadata: Mapping[str, Any]
- 
- 
-diff --git a/pcs/common/pacemaker/tag.py b/pcs/common/pacemaker/tag.py
-index 63c55464f..57f0f0724 100644
---- a/pcs/common/pacemaker/tag.py
-+++ b/pcs/common/pacemaker/tag.py
-@@ -2,13 +2,12 @@ from dataclasses import dataclass
- from typing import Sequence
- 
- from pcs.common.interface.dto import DataTransferObject
--from pcs.common.types import StringSequence
- 
- 
- @dataclass(frozen=True)
- class CibTagDto(DataTransferObject):
-     id: str
--    idref_list: StringSequence
-+    idref_list: list[str]
- 
- 
- @dataclass(frozen=True)
-diff --git a/pcs/lib/permissions/config/types.py b/pcs/lib/permissions/config/types.py
-index cb54e34fa..d0005dbc2 100644
---- a/pcs/lib/permissions/config/types.py
-+++ b/pcs/lib/permissions/config/types.py
-@@ -1,20 +1,16 @@
- from dataclasses import dataclass
--from typing import (
--    Collection,
--    Sequence,
--)
-+from typing import Collection, Sequence
- 
- from pcs.common.permissions.types import (
-     PermissionAccessType,
-     PermissionTargetType,
- )
--from pcs.common.types import StringSequence
- 
- 
- @dataclass(frozen=True)
- class ClusterEntry:
-     name: str
--    nodes: StringSequence
-+    nodes: list[str]
- 
- 
- @dataclass(frozen=True)
-diff --git a/pcs_test/tier0/common/interface/test_dto.py b/pcs_test/tier0/common/interface/test_dto.py
-index 8ea5e5e3a..853112980 100644
---- a/pcs_test/tier0/common/interface/test_dto.py
-+++ b/pcs_test/tier0/common/interface/test_dto.py
-@@ -1,13 +1,7 @@
-+import importlib
- import pkgutil
--from dataclasses import (
--    dataclass,
--    field,
--    is_dataclass,
--)
--from typing import (
--    Any,
--    List,
--)
-+from dataclasses import dataclass, field, is_dataclass
-+from typing import Any
- from unittest import TestCase
- 
- from dacite.exceptions import WrongTypeError
-@@ -25,12 +19,12 @@ from pcs.common.types import CorosyncNodeAddressType
- def _import_all(_path):
-     # arbitrary prefix so it doesn't interact with real import in real tests
-     for module_finder, module_name, is_pkg in pkgutil.walk_packages(
--        _path, prefix="_pcs."
-+        _path, prefix="pcs."
-     ):
--        if module_name.startswith("_pcs.snmp."):
-+        del module_finder, is_pkg
-+        if module_name.startswith("pcs.snmp."):
-             continue
--        del is_pkg
--        module_finder.find_spec(module_name).loader.load_module(module_name)
-+        importlib.import_module(module_name)
- 
- 
- def _all_subclasses(cls):
-@@ -62,7 +56,7 @@ class MyDto2(DataTransferObject):
- @dataclass
- class MyDto3(DataTransferObject):
-     field_g: MyDto2 = field(metadata=meta(name="field-g"))
--    field_h: List[MyDto2]
-+    field_h: list[MyDto2]
-     field_i: int = field(metadata=meta(name="field-i"))
- 
- 
-diff --git a/pcs_test/tier0/lib/permissions/config/test_parser.py b/pcs_test/tier0/lib/permissions/config/test_parser.py
-index 89903a969..a588d13b6 100644
---- a/pcs_test/tier0/lib/permissions/config/test_parser.py
-+++ b/pcs_test/tier0/lib/permissions/config/test_parser.py
-@@ -1,7 +1,7 @@
- import json
--from dataclasses import asdict
- from unittest import TestCase
- 
-+from pcs.common.interface.dto import to_dict
- from pcs.common.permissions.types import (
-     PermissionAccessType,
-     PermissionTargetType,
-@@ -35,12 +35,10 @@ class ParserV2Test(TestCase):
-     def test_format_version_missing(self):
-         with self.assertRaises(ParserError):
-             self._run_parse(
--                asdict(
--                    ConfigV2(
--                        data_version=1,
--                        clusters=[],
--                        permissions=ClusterPermissions(local_cluster=[]),
--                    )
-+                dict(
-+                    data_version=1,
-+                    clusters=[],
-+                    permissions=dict(local_cluster=[]),
-                 )
-             )
- 
-@@ -48,14 +46,10 @@ class ParserV2Test(TestCase):
-         with self.assertRaises(ParserError):
-             self._run_parse(
-                 dict(
--                    **asdict(
--                        ConfigV2(
--                            data_version=1,
--                            clusters=[],
--                            permissions=ClusterPermissions(local_cluster=[]),
--                        )
--                    ),
-+                    data_version=1,
-                     format_version=3,
-+                    clusters=[],
-+                    permissions=dict(local_cluster=[]),
-                 )
-             )
- 
-@@ -63,20 +57,16 @@ class ParserV2Test(TestCase):
-         with self.assertRaises(ParserError):
-             self._run_parse(
-                 dict(
--                    **asdict(
--                        ConfigV2(
--                            data_version=1,
--                            clusters=[
--                                ClusterEntry(
--                                    name="testcluster",
--                                    nodes=["node1", "node2", "node0"],
--                                ),
--                                dict(name="invalid data"),
--                            ],
--                            permissions=ClusterPermissions(local_cluster=[]),
--                        )
--                    ),
-                     format_version=2,
-+                    data_version=1,
-+                    clusters=[
-+                        dict(
-+                            name="testcluster",
-+                            nodes=["node1", "node2", "node0"],
-+                        ),
-+                        dict(name="invalid data"),
-+                    ],
-+                    permissions=dict(local_cluster=[]),
-                 )
-             )
- 
-@@ -112,5 +102,5 @@ class ParserV2Test(TestCase):
-             ),
-         )
-         self.assertEqual(
--            self._run_parse(dict(**asdict(config), format_version=2)), config
-+            self._run_parse(dict(**to_dict(config), format_version=2)), config
-         )
--- 
-2.53.0
-

                 reply	other threads:[~2026-09-16 12:21 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178956127478.1.207924623514939288.rpms-pcs-75c63d554b8b@fedoraproject.org \
    --to=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox