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
46 changes: 45 additions & 1 deletion control_plane/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@
)
from control_plane.workflows.generic_web_deploy import (
GenericWebDeployRequest,
GenericWebDeployStore,
GenericWebPostDeployContext,
GenericWebPostDeployExecutor,
execute_generic_web_deploy,
)
from control_plane.workflows.generic_web_promotion import (
Expand Down Expand Up @@ -347,6 +350,7 @@
)
from control_plane.workflows.odoo_post_deploy import (
OdooPostDeployRequest,
OdooPostDeployResult,
execute_odoo_post_deploy,
)
from control_plane.contracts.odoo_stable_bootstrap import (
Expand Down Expand Up @@ -728,6 +732,7 @@ class _ResolvedProductDriverContext:
lane: ProductLaneProfile | None = None


_ODOO_GENERIC_WEB_POST_DEPLOY_DRIVERS = frozenset({"odoo"})
_LAUNCHPLANE_IMAGE_REFERENCE_ENV_KEY = "DOCKER_IMAGE_REFERENCE"
_LOGGER = logging.getLogger(__name__)
_LAUNCHPLANE_SELF_DEPLOY_OAUTH_ENV_KEYS = frozenset(
Expand Down Expand Up @@ -4988,7 +4993,7 @@ def _accepted_payload(

def _accepted_payload_extra_record_keys(*, route_path: str) -> frozenset[str]:
if route_path == _GENERIC_WEB_ROLLBACK_ROUTE.route_path:
return frozenset({"rollback_status", "deploy_status"})
return frozenset({"rollback_status", "deploy_status", "post_deploy_status"})
return frozenset()


Expand Down Expand Up @@ -6421,6 +6426,40 @@ def _product_driver_route_compatible(
)


def _post_deploy_evidence_from_odoo_result(
result: OdooPostDeployResult,
) -> PostDeployUpdateEvidence:
return PostDeployUpdateEvidence(
attempted=True,
status=result.post_deploy_status,
detail=(
result.error_message
or "Odoo post-deploy completed through the generic-web extension hook."
),
)


def _execute_odoo_generic_web_post_deploy(
control_plane_root: Path,
record_store: GenericWebDeployStore,
context: GenericWebPostDeployContext,
) -> PostDeployUpdateEvidence:
result = execute_odoo_post_deploy(
control_plane_root=control_plane_root,
record_store=record_store,
request=OdooPostDeployRequest(context=context.context, instance=context.instance),
)
return _post_deploy_evidence_from_odoo_result(result)


def _generic_web_post_deploy_executor_for_profile(
profile: LaunchplaneProductProfileRecord,
) -> GenericWebPostDeployExecutor | None:
if profile.driver_id.strip() in _ODOO_GENERIC_WEB_POST_DEPLOY_DRIVERS:
return _execute_odoo_generic_web_post_deploy
return None


def _find_product_profile_lane(
*, profile: LaunchplaneProductProfileRecord, context: str, instance: str
) -> ProductLaneProfile | None:
Expand Down Expand Up @@ -11523,6 +11562,7 @@ def product_action_allowed(
request=generic_web_deploy_request.deploy,
profile=profile,
lane=lane,
post_deploy_executor=_generic_web_post_deploy_executor_for_profile(profile),
)
result = {"deployment_record_id": driver_result.deployment_record_id}
elif path == _GENERIC_WEB_PROD_PROMOTION_ROUTE.route_path:
Expand Down Expand Up @@ -11737,12 +11777,16 @@ def product_action_allowed(
control_plane_root=resolved_root,
record_store=record_store,
request=generic_web_rollback_apply_request.rollback,
post_deploy_executor=_generic_web_post_deploy_executor_for_profile(
resolved_driver_context.profile
),
)
result = {
"generic_web_rollback_plan_id": driver_result.plan_id,
"deployment_record_id": driver_result.deployment_record_id,
"rollback_status": driver_result.rollback_status,
"deploy_status": driver_result.deploy_status,
"post_deploy_status": driver_result.post_deploy_status,
}
elif path in _PREVIEW_DESIRED_STATE_ROUTE_PATHS:
generic_web_desired_state_request, profile, authorization_response = (
Expand Down
15 changes: 9 additions & 6 deletions control_plane/workflows/generic_web_rollback.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class GenericWebRollbackApplyResult(BaseModel):
deployment_record_id: str = ""
rollback_status: Literal["pass", "fail", "blocked"]
deploy_status: Literal["pass", "fail", "skipped"] = "skipped"
post_deploy_status: Literal["pass", "fail", "skipped"] = "skipped"
product: str
context: str
instance: str
Expand Down Expand Up @@ -99,16 +100,18 @@ def execute_generic_web_rollback(
),
post_deploy_executor=post_deploy_executor,
)
rollback_status: Literal["pass", "fail"] = (
"pass"
if deploy_result.deploy_status == "pass"
and deploy_result.post_deploy_status in {"pass", "skipped"}
else "fail"
)
return GenericWebRollbackApplyResult(
plan_id=plan.plan_id,
deployment_record_id=deploy_result.deployment_record_id,
rollback_status=(
"pass"
if deploy_result.deploy_status == "pass"
and deploy_result.post_deploy_status in {"pass", "skipped"}
else "fail"
),
rollback_status=rollback_status,
deploy_status=deploy_result.deploy_status,
post_deploy_status=deploy_result.post_deploy_status,
product=plan.product,
context=plan.context,
instance=plan.instance,
Expand Down
5 changes: 5 additions & 0 deletions docs/dokploy-service-deployments.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ extension writes terminal post-deploy evidence without changing the underlying
deploy status, so operators can distinguish "image deploy failed" from "image
deploy passed but product maintenance failed".

Odoo profiles that execute generic-web deploy or rollback apply use this
extension to run the Odoo post-deploy driver after the provider deploy succeeds.
The Odoo-specific rollback apply route remains available for rollback flows that
still need Odoo release tuple and promotion-state updates.

Promotion uses the same artifact identity and target records. When health URLs
exist, Launchplane verifies the source and destination lane health around the
deployment and writes promotion evidence.
Expand Down
9 changes: 7 additions & 2 deletions docs/driver-descriptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ records post-deploy evidence as `skipped` unless a based driver explicitly
provides a product post-deploy extension. That extension point is the boundary
for product-only work after a provider deploy succeeds; it must return terminal
post-deploy evidence and must keep deploy status distinct from post-deploy
status.
status. Odoo profiles receive this extension when they execute generic-web
deploy, which runs the Odoo post-deploy driver after the provider deploy
succeeds.

The `prod_promotion` action routes to
`POST /v1/drivers/generic-web/prod-promotion`. It promotes a generic-web
Expand All @@ -139,7 +141,10 @@ validation, persists the plan record, and applies ready plans through the normal
generic-web deploy path using the previous immutable artifact identity. Generic
rollback also forwards the generic deploy post-deploy extension hook, so a
based driver can keep product-only post-deploy checks while reusing the common
rollback deployment path once its other invariants are represented. Product
rollback deployment path once its other invariants are represented. Odoo profiles
therefore run Odoo post-deploy through generic rollback apply, but the canonical
Odoo rollback apply route remains product-specific until its release and
promotion bookkeeping is generic-contract represented or explicitly wrapped. Product
drivers keep their own `prod_rollback` action only when they need additional
product-specific gates, such as Odoo backup, release tuple, manifest, migration,
or post-deploy checks. Odoo keeps `POST /v1/drivers/odoo/prod-rollback` as its
Expand Down
3 changes: 3 additions & 0 deletions docs/driver-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ as Odoo override/application maintenance. The extension must return terminal
`PostDeployUpdateEvidence` and must not hide provider deploy status: a failed
extension can fail the lifecycle action while the deployment record still shows
the underlying image deploy as `pass` and the post-deploy evidence as `fail`.
Launchplane wires this extension for Odoo profiles when they execute generic-web
deploy or rollback apply, so Odoo can reuse common provider deployment while its
post-deploy maintenance remains explicit driver behavior.
Do not move a product apply route onto generic deploy until its remaining
release, backup, promotion, migration, and post-deploy invariants are either
represented in generic contracts or still explicitly wrapped by the product
Expand Down
Loading