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