diff --git a/src/DIRAC/ConfigurationSystem/ConfigTemplate.cfg b/src/DIRAC/ConfigurationSystem/ConfigTemplate.cfg index 5776ba856db..b0d1135b6bc 100644 --- a/src/DIRAC/ConfigurationSystem/ConfigTemplate.cfg +++ b/src/DIRAC/ConfigurationSystem/ConfigTemplate.cfg @@ -6,6 +6,9 @@ Services { HandlerPath = DIRAC/ConfigurationSystem/Service/TornadoConfigurationHandler.py Port = 9135 + # Set to True to make a DiracX model validation on commits + # If validation fails, commit will also failed + VerifyDiracXSyncOnCommit = False # Subsection to configure authorization over the service Authorization { diff --git a/src/DIRAC/ConfigurationSystem/private/Modificator.py b/src/DIRAC/ConfigurationSystem/private/Modificator.py index 04f8afc70e4..7f07a506075 100755 --- a/src/DIRAC/ConfigurationSystem/private/Modificator.py +++ b/src/DIRAC/ConfigurationSystem/private/Modificator.py @@ -1,13 +1,16 @@ """ This is the guy that actually modifies the content of the CS """ -import zlib -import difflib import datetime +import difflib +import zlib from diraccfg import CFG -from DIRAC.Core.Utilities import List + +from DIRAC import S_ERROR from DIRAC.ConfigurationSystem.Client.ConfigurationData import gConfigurationData from DIRAC.Core.Security.ProxyInfo import getProxyInfo +from DIRAC.Core.Utilities import List +from DIRAC.FrameworkSystem.Utilities.diracx import diracxVerifyConfig class Modificator: @@ -239,6 +242,11 @@ def __str__(self): return str(self.cfgData) def commit(self): + retOpt = self.getValue("/Systems/Configuration/Services/Server/VerifyDiracXSyncOnCommit") + if retOpt == "True": + resVerif = diracxVerifyConfig(self.cfgData) + if not resVerif["OK"]: + return S_ERROR(resVerif["Message"]) compressedData = zlib.compress(str(self.cfgData).encode(), 9) return self.rpcClient.commitNewData(compressedData) diff --git a/src/DIRAC/FrameworkSystem/Utilities/diracx.py b/src/DIRAC/FrameworkSystem/Utilities/diracx.py index 631e4167c3e..766ed48b95d 100644 --- a/src/DIRAC/FrameworkSystem/Utilities/diracx.py +++ b/src/DIRAC/FrameworkSystem/Utilities/diracx.py @@ -1,20 +1,25 @@ -import requests - -from cachetools import TTLCache, LRUCache, cached -from cachetools.keys import hashkey +import os +import re +import subprocess +from collections.abc import Generator +from contextlib import contextmanager from pathlib import Path from tempfile import NamedTemporaryFile +import tempfile from typing import Any -from collections.abc import Generator -from DIRAC import gConfig -from DIRAC.ConfigurationSystem.Client.Helpers import Registry -from contextlib import contextmanager +import requests +from cachetools import LRUCache, TTLCache, cached +from cachetools.keys import hashkey +from diracx.cli.internal.legacy import _apply_fixes +from diracx.core.config.schema import Config as DiracxConfig +from diracx.core.models import TokenResponse from diracx.core.preferences import DiracxPreferences - from diracx.core.utils import write_credentials +from pydantic import ValidationError -from diracx.core.models import TokenResponse +from DIRAC import S_ERROR, S_OK, gConfig +from DIRAC.ConfigurationSystem.Client.Helpers import Registry try: from diracx.client.sync import SyncDiracClient @@ -104,3 +109,29 @@ def TheImpersonator(credDict: dict[str, Any], *, source: str = "") -> Generator[ client.__enter__() diracx_client_cache[token_location] = client yield client + + +def diracxVerifyConfig(cfgData): + """Verify CS config using DiracX config validation + + Args: + cfgData: CFG data + + Returns: + S_OK | S_ERROR: Value: diracx Config validation + """ + os.environ["DIRAC_COMPAT_ENABLE_CS_CONVERSION"] = "true" + with tempfile.NamedTemporaryFile() as temp_cfg: + with tempfile.NamedTemporaryFile() as temp_diracx_cfg: + cfgData.writeToFile(temp_cfg.name) + cmd = ["dirac", "internal", "legacy", "cs-sync", temp_cfg.name, temp_diracx_cfg.name] + res = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + os.environ.pop("DIRAC_COMPAT_ENABLE_CS_CONVERSION") + if res.returncode == 0: + return S_OK(res.stdout) + else: + err = res.stderr.strip() + match = re.search(r"(ValidationError:.*)", err, flags=re.DOTALL) + if match: + return S_ERROR(match.group(1)) + return S_ERROR(err)