diff --git a/control_plane/workflows/generic_web_deploy.py b/control_plane/workflows/generic_web_deploy.py index 3c4e7ee..8c0f416 100644 --- a/control_plane/workflows/generic_web_deploy.py +++ b/control_plane/workflows/generic_web_deploy.py @@ -366,22 +366,21 @@ def execute_generic_web_deploy( ) deploy_completed = True if post_deploy_executor is not None: - post_deploy_update = _terminal_post_deploy_update( - post_deploy_executor( - control_plane_root, - record_store, - GenericWebPostDeployContext( - product=resolved_profile.product, - context=resolved_lane.context, - instance=resolved_lane.instance, - deployment_record_id=record_id, - target_name=resolved_target.target_name, - target_type=resolved_target.target_type, - target_id=resolved_target.target_id, - artifact_id=ship_request.artifact_id, - source_git_ref=ship_request.source_git_ref, - ), - ) + post_deploy_update = _run_post_deploy_extension( + control_plane_root=control_plane_root, + record_store=record_store, + context=GenericWebPostDeployContext( + product=resolved_profile.product, + context=resolved_lane.context, + instance=resolved_lane.instance, + deployment_record_id=record_id, + target_name=resolved_target.target_name, + target_type=resolved_target.target_type, + target_id=resolved_target.target_id, + artifact_id=ship_request.artifact_id, + source_git_ref=ship_request.source_git_ref, + ), + post_deploy_executor=post_deploy_executor, ) if post_deploy_update.status == "fail": raise click.ClickException( @@ -396,18 +395,36 @@ def execute_generic_web_deploy( status="fail", detail=str(exc), ) - record_store.write_deployment_record( - build_deployment_record( - request=ship_request, - record_id=record_id, - deployment_id="control-plane-dokploy", - deployment_status=deployment_status, - started_at=started_at, - finished_at=finished_at, - resolved_target=resolved_target, - post_deploy_update=post_deploy_update, + runtime_identity = ( + _build_runtime_identity( + profile=resolved_profile, + lane=resolved_lane, + ship_request=ship_request, + deployment_record_id=record_id, + deployed_at=finished_at, ) + if deploy_completed + else None ) + deployment_record = build_deployment_record( + request=ship_request, + record_id=record_id, + deployment_id="control-plane-dokploy", + deployment_status=deployment_status, + started_at=started_at, + finished_at=finished_at, + resolved_target=resolved_target, + post_deploy_update=post_deploy_update, + runtime_identity=runtime_identity, + ) + record_store.write_deployment_record(deployment_record) + if deploy_completed: + record_store.write_environment_inventory( + build_environment_inventory( + deployment_record=deployment_record, + updated_at=finished_at, + ) + ) return GenericWebDeployResult( deployment_record_id=record_id, deploy_status=deployment_status, @@ -479,3 +496,20 @@ def _terminal_post_deploy_update( "Generic web post-deploy extensions must return terminal evidence." ) return post_deploy_update + + +def _run_post_deploy_extension( + *, + control_plane_root: Path, + record_store: GenericWebDeployStore, + context: GenericWebPostDeployContext, + post_deploy_executor: GenericWebPostDeployExecutor, +) -> PostDeployUpdateEvidence: + try: + return _terminal_post_deploy_update( + post_deploy_executor(control_plane_root, record_store, context) + ) + except click.ClickException: + raise + except Exception as exc: + raise click.ClickException(str(exc)) from exc diff --git a/tests/test_generic_web_deploy.py b/tests/test_generic_web_deploy.py index 380744e..c808565 100644 --- a/tests/test_generic_web_deploy.py +++ b/tests/test_generic_web_deploy.py @@ -268,7 +268,60 @@ def post_deploy( self.assertEqual(result.error_message, "post deploy failed") self.assertEqual(store.deployments[0].deploy.status, "pass") self.assertEqual(store.deployments[0].post_deploy_update.status, "fail") - self.assertEqual(store.inventories, []) + self.assertIsNotNone(store.deployments[0].runtime_identity) + self.assertEqual(len(store.inventories), 1) + self.assertEqual(store.inventories[0].post_deploy_update.status, "fail") + self.assertEqual( + store.inventories[0].deployment_record_id, + store.deployments[0].record_id, + ) + + def test_execute_generic_web_deploy_records_unexpected_post_deploy_exception( + self, + ) -> None: + store = _GenericWebDeployStore(_profile()) + + def post_deploy( + _root: Path, + _store: GenericWebDeployStore, + _context: GenericWebPostDeployContext, + ) -> PostDeployUpdateEvidence: + raise RuntimeError("unexpected post deploy failure") + + with ( + patch( + "control_plane.workflows.generic_web_deploy.control_plane_dokploy.read_control_plane_dokploy_source_of_truth", + return_value=_source_of_truth(), + ), + patch( + "control_plane.workflows.generic_web_deploy.control_plane_runtime_environments.resolve_runtime_environment_values", + return_value={}, + ), + patch( + "control_plane.workflows.generic_web_deploy.control_plane_dokploy.read_dokploy_config", + return_value=("https://dokploy.example", "token"), + ), + patch("control_plane.workflows.generic_web_deploy.execute_dokploy_artifact_deploy"), + ): + result = execute_generic_web_deploy( + control_plane_root=Path("."), + record_store=store, + request=_request(), + post_deploy_executor=post_deploy, + ) + + self.assertEqual(result.deploy_status, "pass") + self.assertEqual(result.post_deploy_status, "fail") + self.assertEqual(result.error_message, "unexpected post deploy failure") + self.assertEqual(store.deployments[0].deploy.status, "pass") + self.assertEqual(store.deployments[0].post_deploy_update.status, "fail") + self.assertEqual( + store.deployments[0].post_deploy_update.detail, + "unexpected post deploy failure", + ) + self.assertIsNotNone(store.deployments[0].runtime_identity) + self.assertEqual(len(store.inventories), 1) + self.assertEqual(store.inventories[0].post_deploy_update.status, "fail") def test_execute_generic_web_deploy_treats_returned_post_deploy_failure_as_failed_extension( self, @@ -314,7 +367,9 @@ def post_deploy( self.assertEqual(store.deployments[0].deploy.status, "pass") self.assertEqual(store.deployments[0].post_deploy_update.status, "fail") self.assertEqual(store.deployments[0].post_deploy_update.detail, "returned failure") - self.assertEqual(store.inventories, []) + self.assertIsNotNone(store.deployments[0].runtime_identity) + self.assertEqual(len(store.inventories), 1) + self.assertEqual(store.inventories[0].post_deploy_update.status, "fail") def test_execute_generic_web_deploy_uses_qualified_bare_tag(self) -> None: store = _GenericWebDeployStore(_profile())