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
7 changes: 6 additions & 1 deletion docs/tooling/workspace-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ Notes
`launchplane_settings`. `config_parameters` tables write Odoo
`ir.config_parameter` keys, while `addon_settings.<addon>` tables write
supported addon settings such as `authentik_sso` values.
- Non-local Launchplane-managed instances (`dev`, `testing`, and `prod`) always
prepend `launchplane_settings` and `disable_odoo_online` to the resolved Odoo
install module list. Artifact inputs or base images make addon files
available, but this install list is what activates those modules in each
database.
- When a tenant repo contains `website-bootstrap.toml` beside `workspace.toml`,
runtime selection also folds that non-secret website intent into the same
typed payload. The bootstrap contract can add install modules, provide the
Expand All @@ -198,7 +203,7 @@ Notes
continue. `LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED=true` additionally requires
a non-empty `website_bootstrap` object in that payload. These flags are
runtime assertions supplied by Launchplane-managed records or operator input;
local/dev runtimes remain optional unless a caller explicitly sets them.
local runtimes remain optional unless a caller explicitly sets them.
- Legacy setting-shaped inputs such as `ENV_OVERRIDE_CONFIG_PARAM__*`,
`ENV_OVERRIDE_AUTHENTIK__*`, and `ENV_OVERRIDE_SHOPIFY__*` are still accepted
as a compatibility input and converted into the same typed payload, but they
Expand Down
8 changes: 8 additions & 0 deletions odoo_devkit/local_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@
DEFAULT_ODOO_BASE_RUNTIME_IMAGE = "registry.invalid/private-enterprise-runtime:19.0-runtime"
DEFAULT_ODOO_BASE_DEVTOOLS_IMAGE = "registry.invalid/private-enterprise-devtools:19.0-devtools"
CONTROL_PLANE_ROOT_ENV_VAR = "ODOO_CONTROL_PLANE_ROOT"
LAUNCHPLANE_MANAGED_INSTANCE_NAMES = {"dev", "testing", "prod"}
LAUNCHPLANE_REQUIRED_ODOO_MODULES = ("launchplane_settings", "disable_odoo_online")

_REGISTRY_LOGINS_DONE: set[tuple[str, str, str]] = set()
_VERIFIED_IMAGE_ACCESS: set[str] = set()
Expand Down Expand Up @@ -1530,6 +1532,8 @@ def resolve_runtime_selection(
effective_install_modules = merge_effective_modules(
context_definition=context_definition, instance_definition=instance_definition
)
if launchplane_managed_instance(instance_name):
effective_install_modules = dedupe_module_names((*LAUNCHPLANE_REQUIRED_ODOO_MODULES, *effective_install_modules))
if website_bootstrap is not None:
effective_install_modules = dedupe_module_names((*effective_install_modules, *website_bootstrap.install_modules))
effective_source_repositories = resolve_runtime_source_repositories(
Expand Down Expand Up @@ -1588,6 +1592,10 @@ def merge_effective_modules(*, context_definition: ContextDefinition, instance_d
return tuple(effective_install_modules)


def launchplane_managed_instance(instance_name: str) -> bool:
return instance_name.strip().lower() in LAUNCHPLANE_MANAGED_INSTANCE_NAMES


def dedupe_module_names(module_names: Iterable[str]) -> tuple[str, ...]:
effective_module_names: list[str] = []
for module_name in module_names:
Expand Down
80 changes: 80 additions & 0 deletions tests/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,86 @@ def test_resolve_runtime_selection_tracks_effective_source_selectors(self) -> No
"example/testing_selector@release-19",
),
)
self.assertEqual(
selection.effective_install_modules,
("launchplane_settings", "disable_odoo_online", "opw_custom"),
)

def test_resolve_runtime_selection_keeps_local_modules_explicit(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
temp_root = Path(temporary_directory)
stack_definition = local_runtime.parse_stack_definition(
{
"schema_version": 1,
"odoo_version": "19.0",
"addons_path": ["/odoo/addons", "/opt/project/addons"],
"contexts": {
"opw": {
"database": "opw",
"install_modules": ["opw_custom"],
"instances": {
"local": {},
},
}
},
},
stack_file_path=temp_root / "platform" / "stack.toml",
)

selection = local_runtime.resolve_runtime_selection(
stack_definition=stack_definition,
artifact_inputs_definition=None,
context_name="opw",
instance_name="local",
repo_root=temp_root,
)

self.assertEqual(selection.effective_install_modules, ("opw_custom",))

def test_resolve_runtime_selection_orders_managed_and_bootstrap_modules(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
temp_root = Path(temporary_directory)
stack_definition = local_runtime.parse_stack_definition(
{
"schema_version": 1,
"odoo_version": "19.0",
"addons_path": ["/odoo/addons", "/opt/project/addons"],
"contexts": {
"opw": {
"database": "opw",
"install_modules": ["opw_custom"],
"instances": {
"testing": {},
},
}
},
},
stack_file_path=temp_root / "platform" / "stack.toml",
)
website_bootstrap = local_runtime.parse_website_bootstrap_definition(
{
"schema_version": 1,
"tenant": "opw",
"odoo": {"install_modules": ["opw_custom", "website_sale"]},
"website": {"name": "OPW"},
},
bootstrap_path=temp_root / "website-bootstrap.toml",
context_name="opw",
)

selection = local_runtime.resolve_runtime_selection(
stack_definition=stack_definition,
artifact_inputs_definition=None,
context_name="opw",
instance_name="testing",
repo_root=temp_root,
website_bootstrap=website_bootstrap,
)

self.assertEqual(
selection.effective_install_modules,
("launchplane_settings", "disable_odoo_online", "opw_custom", "website_sale"),
)

def test_native_runtime_publish_prefers_exact_control_plane_refs_over_stack_defaults(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
Expand Down