Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions exordos_core/cmd/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,10 @@ def _ensure_bootstrap_repo(manifests_dir: str) -> repo_models.Repository:
MIGRATION_REPO_NAME = "migration-dummy-repo"
CORE_CONFIG_PATH = "/etc/exordos_core/exordos_core.conf"
UA_CONFIG_PATH = "/etc/exordos_universal_agent/exordos_universal_agent.conf"
CORE_CONFIG_DATA_PATH = "/var/lib/exordos/data/etc/exordos_core/exordos_core.conf"
UA_CONFIG_DATA_PATH = (
"/var/lib/exordos/data/etc/exordos_universal_agent/exordos_universal_agent.conf"
)

_LAUNCHPAD_SECTION = """\
[launchpad]
Expand All @@ -359,6 +363,21 @@ def _ensure_bootstrap_repo(manifests_dir: str) -> repo_models.Repository:
"""


def _sync_config_to_data_path(content: str, data_path: str) -> None:
"""Write the same config content to the mirrored data path.

Ensures config files under /var/lib/exordos/data/etc/ stay in sync
with their /etc/ counterparts.
"""
try:
os.makedirs(os.path.dirname(data_path), exist_ok=True)
with open(data_path, "w", encoding="utf-8") as f:
f.write(content)
LOG.info("Synced config to %s", data_path)
except OSError as e:
LOG.warning("Failed to sync config to %s: %s", data_path, e)


def _migrate_installed_elements_configs() -> None:
"""Migrate config files for repo proxy support.

Expand All @@ -382,6 +401,7 @@ def _migrate_installed_elements_configs() -> None:
with open(CORE_CONFIG_PATH, "w", encoding="utf-8") as f:
f.write(content)
LOG.info("Added [launchpad] section to %s", CORE_CONFIG_PATH)
_sync_config_to_data_path(content, CORE_CONFIG_DATA_PATH)
Comment thread
akremenetsky marked this conversation as resolved.
else:
LOG.info("[launchpad] section already exists in %s", CORE_CONFIG_PATH)

Expand Down Expand Up @@ -417,6 +437,7 @@ def _migrate_installed_elements_configs() -> None:
"Updated [universal_agent_scheduler] section in %s",
UA_CONFIG_PATH,
)
_sync_config_to_data_path(new_content, UA_CONFIG_DATA_PATH)
else:
LOG.info(
"[universal_agent_scheduler] section already up to date in %s",
Expand Down
18 changes: 4 additions & 14 deletions exordos_core/repo/agents/universal/drivers/repo_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,10 @@ def _make_installed_manifest(
project_id=em_manifest.project_id,
)

if em_manifest.openapi_spec is not None:
installed_manifest.manifest["openapi_spec"] = em_manifest.openapi_spec

if em_manifest.exports:
installed_manifest.manifest["exports"] = em_manifest.exports

if em_manifest.imports:
installed_manifest.manifest["imports"] = em_manifest.imports

if em_manifest.requirements:
installed_manifest.manifest["requirements"] = em_manifest.requirements

if em_manifest.resources:
installed_manifest.manifest["resources"] = em_manifest.resources
for field in re_builder.InstalledManifest.MANIFEST_OPTIONAL_FIELDS:
value = getattr(em_manifest, field)
if value:
installed_manifest.manifest[field] = value

return installed_manifest

Expand Down
19 changes: 18 additions & 1 deletion exordos_core/repo/builders/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ class InstalledManifest(
linked.
"""

MANIFEST_OPTIONAL_FIELDS = (
"openapi_spec",
"exports",
"imports",
"requirements",
"resources",
)

version = properties.property(
ra_types.String(min_length=5, max_length=64), required=True
)
Expand Down Expand Up @@ -101,12 +109,21 @@ def get_resource_target_fields(self) -> tp.Collection[str]:
@classmethod
def from_repo_element(cls, element: models.RepoElement) -> "InstalledManifest":
uuid = sys_uuid.UUID(str(element.manifest.get("uuid") or element.uuid))
# Filter out empty optional fields to keep the manifest consistent
# with _make_installed_manifest in the repo element driver, which
# also skips empty values. Without this, the agent detects a diff
# between target and actual resources on every iteration.
manifest = {
k: v
for k, v in element.manifest.items()
if k not in cls.MANIFEST_OPTIONAL_FIELDS or v
}
return cls(
uuid=uuid,
name=element.name,
description=element.manifest.get("description", ""),
version=element.version,
manifest=element.manifest,
manifest=manifest,
project_id=element.project_id,
)

Expand Down
44 changes: 44 additions & 0 deletions exordos_core/tests/unit/repo/test_element_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,50 @@ def test_from_repo_element_without_manifest_uuid(self):
assert result.name == element.name
assert result.version == "2.0.0"

def test_from_repo_element_filters_empty_optional_fields(self):
"""Empty optional fields should be excluded from the manifest."""
element_uuid = sys_uuid.uuid4()
element = FakeElement(version="1.0.0")
element.uuid = element_uuid
element.manifest = {
"uuid": element_uuid,
"key": "value",
"exports": {},
"imports": {},
"requirements": {},
"resources": {},
"openapi_spec": None,
}
element.project_id = sys_uuid.uuid4()

result = builder_element.InstalledManifest.from_repo_element(element)

assert "exports" not in result.manifest
assert "imports" not in result.manifest
assert "requirements" not in result.manifest
assert "resources" not in result.manifest
assert "openapi_spec" not in result.manifest
assert result.manifest["key"] == "value"

def test_from_repo_element_keeps_non_empty_optional_fields(self):
"""Non-empty optional fields should be preserved in the manifest."""
element_uuid = sys_uuid.uuid4()
element = FakeElement(version="1.0.0")
element.uuid = element_uuid
element.manifest = {
"uuid": element_uuid,
"exports": {"exp1": {}},
"resources": {"res1": {}},
"openapi_spec": {"paths": {}},
}
element.project_id = sys_uuid.uuid4()

result = builder_element.InstalledManifest.from_repo_element(element)

assert result.manifest["exports"] == {"exp1": {}}
assert result.manifest["resources"] == {"res1": {}}
assert result.manifest["openapi_spec"] == {"paths": {}}

def test_get_resource_target_fields(self):
manifest = builder_element.InstalledManifest(
name="test",
Expand Down
19 changes: 19 additions & 0 deletions exordos_core/tests/unit/repo/test_repo_element_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ def test_without_repo_element_without_em_element(self):
}
assert result.manifest == expected_manifest

def test_with_non_empty_optional_fields(self):
"""Should include non-empty optional fields in the manifest."""
client = self._make_client()
em_manifest = self._make_em_manifest()
em_manifest.exports = {"exp1": {}}
em_manifest.imports = {"imp1": {}}
em_manifest.requirements = {"dep": {"from_version": "1.0.0"}}
em_manifest.resources = {"res1": {}}
em_manifest.openapi_spec = {"paths": {}}
em_element = self._make_em_element()

result = client._make_installed_manifest(em_manifest, em_element)

assert result.manifest["exports"] == {"exp1": {}}
assert result.manifest["imports"] == {"imp1": {}}
assert result.manifest["requirements"] == {"dep": {"from_version": "1.0.0"}}
assert result.manifest["resources"] == {"res1": {}}
assert result.manifest["openapi_spec"] == {"paths": {}}


class TestMakeEmManifest:
"""Tests for RepoEmBackendClient._make_em_manifest."""
Expand Down