Skip to content
Closed
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
2 changes: 2 additions & 0 deletions etc/exordos_core/core_agent.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ em_core_dns_domains_records = exordos_core.user_api.dns.dm.models:Record
em_core_iam_organizations = exordos_core.user_api.iam.dm.models:Organization
em_core_iam_organizations_members = exordos_core.user_api.iam.dm.models:OrganizationMember
em_core_iam_roles = exordos_core.user_api.iam.dm.models:Role
em_core_iam_role_bindings = exordos_core.user_api.iam.dm.models:RoleBinding
em_core_iam_rolebinding = exordos_core.user_api.iam.dm.models:RoleBinding
em_core_iam_projects = exordos_core.user_api.iam.dm.models:Project
em_core_iam_permissions = exordos_core.user_api.iam.dm.models:Permission
em_core_iam_permission_bindings = exordos_core.user_api.iam.dm.models:PermissionBinding
em_core_iam_permissionbinding = exordos_core.user_api.iam.dm.models:PermissionBinding
em_core_vs_profiles = exordos_core.vs.dm.models:Profile
em_core_vs_variables = exordos_core.vs.dm.models:Variable
Expand Down
4 changes: 2 additions & 2 deletions exordos/manifests/examples/user.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ resources:
last_name: "Doe"
description: "production engineer"

$core.iam.rolebinding:
$core.iam.role_bindings:
jdoe_global_role_binding:
role: "726f6c65-0000-0000-0000-000000000002"
user: $core.iam.users.$jdoe:uuid
Expand All @@ -42,4 +42,4 @@ resources:
jdoe_organizations_member:
organization: "$core.iam.organizations.$jdoe_corp:uuid"
user: $core.iam.users.$jdoe:uuid
role: "OWNER"
role: "OWNER"
67 changes: 67 additions & 0 deletions exordos_core/cmd/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,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"
CORE_AGENT_CONFIG_PATH = "/etc/exordos_core/core_agent.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"
CORE_AGENT_CONFIG_DATA_PATH = "/var/lib/exordos/data/etc/exordos_core/core_agent.conf"
UA_CONFIG_DATA_PATH = (
"/var/lib/exordos/data/etc/exordos_universal_agent/exordos_universal_agent.conf"
)
Expand Down Expand Up @@ -374,6 +376,70 @@ def _migrate_installed_elements_configs() -> None:
"""
_UA_BORDER_DRIVER_LINE = " BorderAgentCapabilityDriver,\n"

_CORE_AGENT_IAM_BINDING_KINDS = (
("em_core_iam_role_bindings", "em_core_iam_rolebinding"),
("em_core_iam_permission_bindings", "em_core_iam_permissionbinding"),
)


def _ensure_core_agent_config_current() -> None:
"""Add canonical IAM binding kinds to an existing core-agent config.

Older persisted configs register only the deprecated singular kinds. Keep
those aliases for existing manifests and add the plural kinds emitted by
the current manifest schema.
"""
try:
with open(CORE_AGENT_CONFIG_PATH, "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
LOG.warning("Core agent config not found: %s", CORE_AGENT_CONFIG_PATH)
return

new_content = content
for canonical_kind, compatibility_kind in _CORE_AGENT_IAM_BINDING_KINDS:
if re.search(rf"^{re.escape(canonical_kind)}\s*=", new_content, re.MULTILINE):
continue

compatibility_line = re.search(
rf"^{re.escape(compatibility_kind)}(?P<assignment>\s*=.*)$",
new_content,
re.MULTILINE,
)
if compatibility_line is None:
LOG.warning(
"Could not add %s because compatibility kind %s is absent in %s",
canonical_kind,
compatibility_kind,
CORE_AGENT_CONFIG_PATH,
)
continue

canonical_line = canonical_kind + compatibility_line.group("assignment")
new_content = new_content.replace(
compatibility_line.group(0),
canonical_line + "\n" + compatibility_line.group(0),
1,
)

if new_content == content:
LOG.info("Core agent config already up to date in %s", CORE_AGENT_CONFIG_PATH)
return

with open(CORE_AGENT_CONFIG_PATH, "w", encoding="utf-8") as f:
f.write(new_content)
LOG.info("Updated core agent config %s", CORE_AGENT_CONFIG_PATH)
_sync_config_to_data_path(new_content, CORE_AGENT_CONFIG_DATA_PATH)

try:
subprocess.run(
["systemctl", "try-restart", "ec-core-agent"],
check=True,
)
LOG.info("Restarted core agent service to apply the new config")
except (OSError, subprocess.CalledProcessError) as e:
LOG.warning("Failed to restart core agent service: %s", e)


def _ensure_ua_config_current() -> None:
"""Bring the universal agent config up to date with this image.
Expand Down Expand Up @@ -736,6 +802,7 @@ def main() -> None:
# working installations are migrated.
_migrate_installed_elements_to_repo()

_ensure_core_agent_config_current()
_ensure_ua_config_current()

if not os.path.exists(SPEC_PATH):
Expand Down
77 changes: 77 additions & 0 deletions exordos_core/tests/unit/cmd/test_bootstrap_core_agent_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2026 Genesis Corporation.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from unittest import mock

from exordos_core.cmd import bootstrap

_OLD_CORE_AGENT_CONFIG = """\
[models]
em_core_iam_roles = exordos_core.user_api.iam.dm.models:Role
em_core_iam_rolebinding = exordos_core.user_api.iam.dm.models:RoleBinding
em_core_iam_permissions = exordos_core.user_api.iam.dm.models:Permission
em_core_iam_permissionbinding = exordos_core.user_api.iam.dm.models:PermissionBinding
"""


def _run(tmp_path):
etc_path = tmp_path / "core_agent.conf"
data_path = tmp_path / "data" / "core_agent.conf"
with (
mock.patch.object(bootstrap, "CORE_AGENT_CONFIG_PATH", str(etc_path)),
mock.patch.object(bootstrap, "CORE_AGENT_CONFIG_DATA_PATH", str(data_path)),
mock.patch.object(bootstrap.subprocess, "run") as run,
):
bootstrap._ensure_core_agent_config_current()
return etc_path, data_path, run


def test_adds_canonical_iam_binding_kinds_to_old_config(tmp_path):
etc_path = tmp_path / "core_agent.conf"
etc_path.write_text(_OLD_CORE_AGENT_CONFIG, encoding="utf-8")

etc_path, data_path, run = _run(tmp_path)

content = etc_path.read_text(encoding="utf-8")
assert "em_core_iam_role_bindings =" in content
assert "em_core_iam_permission_bindings =" in content
assert "em_core_iam_rolebinding =" in content
assert "em_core_iam_permissionbinding =" in content
assert data_path.read_text(encoding="utf-8") == content
run.assert_called_once_with(
["systemctl", "try-restart", "ec-core-agent"], check=True
)


def test_noop_on_current_config(tmp_path):
etc_path = tmp_path / "core_agent.conf"
etc_path.write_text(_OLD_CORE_AGENT_CONFIG, encoding="utf-8")
_run(tmp_path)
content = etc_path.read_text(encoding="utf-8")

etc_path, data_path, run = _run(tmp_path)

assert etc_path.read_text(encoding="utf-8") == content
assert data_path.read_text(encoding="utf-8") == content
run.assert_not_called()


def test_missing_config_is_skipped(tmp_path):
etc_path, data_path, run = _run(tmp_path)

assert not etc_path.exists()
assert not data_path.exists()
run.assert_not_called()