]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
python-common: multi-line for yaml 68034/head
authorTimothy Q Nguyen <timqn22@gmail.com>
Thu, 26 Mar 2026 19:16:51 +0000 (12:16 -0700)
committerTimothy Q Nguyen <timqn22@gmail.com>
Tue, 7 Apr 2026 22:46:49 +0000 (15:46 -0700)
Currently when running the command ceph orch ls --export
multi-line strings are processed using default interpretation
meaning they could be printed with quotations instead of
the expected | . This causes a hassle for users who want to
copy their specifications to new yaml files as they have to
manually modify the yaml to remove the quotes and add | .
My code fixes this by modifying the yaml representer to
check for multi-line strings and wrapping them in a
YamlLiteralString class which will mark the multi-line string
to be processed with | . I've also added unit tests.

Signed-off-by: Timothy Q Nguyen <timqn22@gmail.com>
src/python-common/ceph/deployment/service_spec.py
src/python-common/ceph/tests/test_service_spec.py

index 2f6d6f944d2f42f8e5f0da83906d0cfa6b9d2d33..9102114a0a800ef8bddd2fb07c225c0bfeda168e 100644 (file)
@@ -94,6 +94,19 @@ def handle_type_error(method: FuncT) -> FuncT:
     return cast(FuncT, inner)
 
 
+class YamlLiteralString(str):
+    """
+    Class used as marker for yaml representer to properly format
+    multi-line strings for yaml export
+    """
+    @staticmethod
+    def represent_as_literal(dumper: 'yaml.SafeDumper', data: str) -> Any:
+        return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
+
+
+yaml.add_representer(YamlLiteralString, YamlLiteralString.represent_as_literal)
+
+
 class HostPlacementSpec(NamedTuple):
     hostname: str
     network: str
@@ -1321,7 +1334,15 @@ class ServiceSpec(object):
 
     @staticmethod
     def yaml_representer(dumper: 'yaml.SafeDumper', data: 'ServiceSpec') -> Any:
-        return dumper.represent_dict(cast(Mapping, data.to_json().items()))
+        json_data = data.to_json()
+        spec = json_data.get('spec', {})
+
+        # Marking multi-line strings with the YamlLiteralString class
+        for key, value in spec.items():
+            if isinstance(value, str) and '\n' in value:
+                spec[key] = YamlLiteralString(value)
+
+        return dumper.represent_dict(cast(Mapping, json_data.items()))
 
 
 yaml.add_representer(ServiceSpec, ServiceSpec.yaml_representer)
index f4628051530eb7ce544693df1633a1cbfbecc1ee..559d082340ece406fec5711b2e797866abc657c2 100644 (file)
@@ -18,6 +18,7 @@ from ceph.deployment.service_spec import (
     PrometheusSpec,
     RGWSpec,
     ServiceSpec,
+    YamlLiteralString,
 )
 from ceph.deployment.drive_group import DriveGroupSpec
 from ceph.deployment.hostspec import SpecValidationError
@@ -1422,3 +1423,37 @@ def test_non_osd_services_do_not_get_default_termination_if_not_provided(spec_da
 
     spec_section = j.get('spec', {})
     assert 'termination_grace_period_seconds' not in spec_section
+
+def test_yaml_literal_string_class_represents_multiline_strings_as_literal():
+    multiline_string = "test1\ntest2\n"
+    dumped = yaml.dump(YamlLiteralString(multiline_string))
+
+    assert dumped.startswith('|')
+
+def test_yaml_representer_can_handle_multiline_strings_for_export():
+    spec_data = """
+service_type: iscsi
+service_id: iscsi
+placement:
+  label: iscsi
+spec:
+  pool: testpool
+  ssl_cert: |
+    -----BEGIN CERTIFICATE-----
+    FILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLER
+    FILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLER
+    -----END CERTIFICATE-----
+  ssl_key: |
+    -----BEGIN PRIVATE KEY-----
+    FILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLER
+    FILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLERFILLER
+    -----END PRIVATE KEY-----
+"""
+
+    data = yaml.safe_load(spec_data)
+    spec_obj = ServiceSpec.from_json(data)
+
+    dumped = yaml.dump(spec_obj, default_flow_style=False)
+
+    assert 'ssl_cert: |' in dumped
+    assert 'ssl_key: |' in dumped