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
38 changes: 38 additions & 0 deletions control_plane/dokploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@
"ODOO_FILESTORE_PATH",
"ODOO_DATA_WORKFLOW_LOCK_FILE",
}
ODOO_INSTANCE_OVERRIDES_PAYLOAD_ENV_KEY = "ODOO_INSTANCE_OVERRIDES_PAYLOAD_B64"
LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED_ENV_KEY = "LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED"
LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED_ENV_KEY = "LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED"
ODOO_RUNTIME_OVERRIDE_TARGET_ENV_KEYS = frozenset(
{
ODOO_INSTANCE_OVERRIDES_PAYLOAD_ENV_KEY,
LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED_ENV_KEY,
LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED_ENV_KEY,
}
)
ODOO_UPSTREAM_RESTORE_WORKFLOW_ENV_KEYS = (
"ODOO_UPSTREAM_HOST",
"ODOO_UPSTREAM_USER",
Expand Down Expand Up @@ -214,6 +224,9 @@ def render_odoo_raw_compose_file(
ODOO_DEV_MODE: ${{ODOO_DEV_MODE:-}}
ODOO_INSTALL_MODULES: ${{ODOO_INSTALL_MODULES:-}}
ODOO_UPDATE_MODULES: ${{ODOO_UPDATE_MODULES:-AUTO}}
ODOO_INSTANCE_OVERRIDES_PAYLOAD_B64: ${{ODOO_INSTANCE_OVERRIDES_PAYLOAD_B64:-}}
LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED: ${{LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED:-}}
LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED: ${{LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED:-}}
ODOO_ADDONS_PATH: ${{ODOO_ADDONS_PATH:-/opt/project/addons,/opt/extra_addons,/opt/launchplane/addons,/opt/enterprise,/odoo/addons}}
ODOO_SERVER_WIDE_MODULES: ${{ODOO_SERVER_WIDE_MODULES:-base,web,launchplane_runtime_health}}
ODOO_DATA_WORKFLOW_LOCK_FILE: ${{ODOO_DATA_WORKFLOW_LOCK_FILE:-/volumes/data/.data_workflow_in_progress}}
Expand Down Expand Up @@ -1661,6 +1674,14 @@ def run_compose_post_deploy_update(
)
resolved_workflow_environment_overrides = dict(workflow_environment_overrides or {})
resolved_required_workflow_environment_keys = tuple(required_workflow_environment_keys)
runtime_override_target_environment = {
key: value
for key, value in resolved_workflow_environment_overrides.items()
if key in ODOO_RUNTIME_OVERRIDE_TARGET_ENV_KEYS
}
for key in ODOO_RUNTIME_OVERRIDE_TARGET_ENV_KEYS:
desired_env_map.pop(key, None)
desired_env_map.update(runtime_override_target_environment)
if run_destructive_restore:
upstream_restore_environment = _resolve_upstream_restore_workflow_environment(
desired_env_map=desired_env_map,
Expand Down Expand Up @@ -1707,6 +1728,23 @@ def run_compose_post_deploy_update(
before_key=deployment_key(latest_compose_deployment),
timeout_seconds=schedule_timeout_seconds,
)
target_payload = fetch_dokploy_target_payload(
host=host,
token=token,
target_type="compose",
target_id=compose_id,
)
refreshed_env_map = parse_dokploy_env_text(str(target_payload.get("env") or ""))
missing_runtime_override_keys = sorted(
key
for key, value in runtime_override_target_environment.items()
if refreshed_env_map.get(key, "") != value
)
if missing_runtime_override_keys:
raise click.ClickException(
"Compose post-deploy update did not persist runtime override key(s): "
+ ", ".join(missing_runtime_override_keys)
)

database_name = desired_env_map.get("ODOO_DB_NAME", "").strip()
if not database_name:
Expand Down
6 changes: 6 additions & 0 deletions control_plane/odoo_instance_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from control_plane.contracts.runtime_environment_record import ScalarValue

ODOO_INSTANCE_OVERRIDES_PAYLOAD_ENV_KEY = "ODOO_INSTANCE_OVERRIDES_PAYLOAD_B64"
LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED_ENV_KEY = "LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED"
LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED_ENV_KEY = "LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED"
ODOO_OVERRIDE_SECRET_ENV_PREFIX = "ODOO_OVERRIDE_SECRET__"
SHOPIFY_ADDON_NAME = "shopify"
SHOPIFY_ACTION_SETTING = "action"
Expand Down Expand Up @@ -274,6 +276,10 @@ def build_post_deploy_environment(
inline_environment: dict[str, str] = {
ODOO_INSTANCE_OVERRIDES_PAYLOAD_ENV_KEY: _encode_post_deploy_payload(payload),
}
if payload.config_parameters or payload.addon_settings:
inline_environment[LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED_ENV_KEY] = "true"
if payload.website_bootstrap_included:
inline_environment[LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED_ENV_KEY] = "true"
return PostDeployOverrideEnvironment(
inline_environment=inline_environment,
required_container_environment_keys=payload.required_container_environment_keys,
Expand Down
45 changes: 45 additions & 0 deletions control_plane/workflows/odoo_stable_target_replacement.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pydantic import BaseModel, ConfigDict

from control_plane import dokploy as control_plane_dokploy
from control_plane import odoo_instance_overrides as control_plane_odoo_instance_overrides
from control_plane import live_target_runtime as control_plane_live_target_runtime
from control_plane import release_tuples as control_plane_release_tuples
from control_plane import runtime_environments as control_plane_runtime_environments
Expand Down Expand Up @@ -1132,6 +1133,36 @@ def execute_odoo_stable_target_replacement_apply(
and normalized_override_record is not odoo_override_record
):
record_store.write_odoo_instance_override_record(normalized_override_record)
runtime_override_environment: dict[str, str] = {}
runtime_override_payload = None
if normalized_override_record is not None and "deploy" in normalized_override_record.apply_on:
runtime_override = control_plane_odoo_instance_overrides.build_post_deploy_environment(
normalized_override_record,
workflow_intent="deploy",
protected_shopify_store_keys=target_record.policies.shopify.protected_store_keys,
)
runtime_override_environment = runtime_override.inline_environment
runtime_override_payload = runtime_override.payload
runtime_source.update(
{
"runtime_override_payload_rendered": "true",
"runtime_override_payload_sha256": runtime_override_payload.wire_sha256,
"runtime_override_count": str(runtime_override_payload.override_count),
"runtime_override_website_bootstrap_included": str(
runtime_override_payload.website_bootstrap_included
).lower(),
"runtime_override_instance_required": runtime_override_environment.get(
control_plane_odoo_instance_overrides.LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED_ENV_KEY,
"false",
),
"runtime_override_website_bootstrap_required": runtime_override_environment.get(
control_plane_odoo_instance_overrides.LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED_ENV_KEY,
"false",
),
}
)
else:
runtime_source["runtime_override_payload_rendered"] = "false"

try:
host, token = control_plane_dokploy.read_dokploy_config(
Expand Down Expand Up @@ -1245,7 +1276,21 @@ def execute_odoo_stable_target_replacement_apply(
str(target_payload.get("env") or "")
)
desired_env_map = dict(current_env_map)
for key in control_plane_dokploy.ODOO_RUNTIME_OVERRIDE_TARGET_ENV_KEYS:
desired_env_map.pop(key, None)
desired_env_map.update(runtime_environment_values)
desired_env_map.update(runtime_override_environment)
if runtime_override_payload is not None:
missing_override_secret_keys = tuple(
key
for key in runtime_override_payload.required_container_environment_keys
if not desired_env_map.get(key, "").strip()
)
if missing_override_secret_keys:
raise click.ClickException(
"Odoo target replacement requires override secret env key(s) before deployment: "
+ ", ".join(missing_override_secret_keys)
)
if desired_env_map.get("ODOO_WEB_COMMAND", "").strip() == "/odoo/odoo-bin":
desired_env_map.pop("ODOO_WEB_COMMAND", None)
desired_env_map["PLATFORM_CONTEXT"] = plan.context
Expand Down
10 changes: 9 additions & 1 deletion docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,15 @@ context only, and `context_instance` has both context and instance.
- `odoo-overrides mark-apply` updates the latest apply status metadata for a
record, giving the future Odoo driver a tested result-write path.
- Compose post-deploy updates consume deploy-phase overrides from these records
and pass them to the Odoo data-workflow runner as one typed payload env var.
and pass them to Odoo as one typed payload env var. Deploy-phase payloads are
persisted to the Dokploy compose target environment before the web container
is redeployed, and the same payload is passed to the Odoo data-workflow
runner for post-deploy maintenance.
- When a deploy-phase payload is expected, Launchplane also persists generic
runtime assertion flags that tell the Odoo runtime to fail closed if managed
instance overrides or website bootstrap data are missing. Launchplane re-reads
the provider target environment after writing it and fails the operation if
the typed payload or assertion flags did not persist.
- Target replacement requests with `data_source_mode="upstream_restore"` use the
guarded post-deploy schedule in destructive restore mode after image deploy so
the devkit restore path performs restore sanitization, website bootstrap,
Expand Down
Loading