From 15d055e6d211dfc73e5b04c7b7bdba5caa7d1082 Mon Sep 17 00:00:00 2001 From: stiv03 Date: Thu, 2 Apr 2026 13:31:00 +0300 Subject: [PATCH 1/3] Add phase-configs target-app resolution in ExecuteTaskStep for blue-green deployments Route hook task execution to idle or live app based on phase-configs in the hook descriptor. Pass idleMtaColor, liveMtaColor, subprocessPhase and phase variables to all hook call activities in BPMN processes. Add determineDeploymentTypeSafely to avoid SERVICE_ID lookup failures in hook subprocesses. --- .../core/model/SupportedParameters.java | 4 +- .../process/steps/ExecuteTaskStep.java | 67 ++++++++++++++++++- .../util/DeploymentTypeDeterminer.java | 4 ++ .../process/backup-existing-app.bpmn | 4 ++ .../controller/process/deploy-app.bpmn | 8 +++ .../process/stop-dependent-modules.bpmn | 3 + .../controller/process/undeploy-app.bpmn | 8 +++ pom.xml | 2 +- 8 files changed, 96 insertions(+), 4 deletions(-) diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/SupportedParameters.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/SupportedParameters.java index 62ff077d27..23b3079142 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/SupportedParameters.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/SupportedParameters.java @@ -217,7 +217,9 @@ public class SupportedParameters { public static final Set DEPENDENCY_PARAMETERS = Set.of(BINDING_NAME, ENV_VAR_NAME, VISIBILITY, USE_LIVE_ROUTES, SERVICE_BINDING_CONFIG, DELETE_SERVICE_KEY_AFTER_DEPLOYMENT); - public static final Set MODULE_HOOK_PARAMETERS = Set.of(NAME, COMMAND, MEMORY, DISK_QUOTA, HOOK_REQUIRES); + public static final String HOOK_TARGET_APP = "hook-target-app"; + + public static final Set MODULE_HOOK_PARAMETERS = Set.of(NAME, COMMAND, MEMORY, DISK_QUOTA, HOOK_REQUIRES, HOOK_TARGET_APP); public static final Set CONFIGURATION_REFERENCE_PARAMETERS = Set.of(PROVIDER_NID, PROVIDER_ID, TARGET, VERSION, DEPRECATED_CONFIG_MTA_ID, DEPRECATED_CONFIG_MTA_VERSION, diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java index 57f2e88526..a333517d90 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java @@ -11,10 +11,13 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientFactory; +import org.cloudfoundry.multiapps.controller.core.model.ApplicationColor; +import org.cloudfoundry.multiapps.controller.core.model.HookPhaseProcessType; import org.cloudfoundry.multiapps.controller.core.security.token.TokenService; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.mta.model.Hook; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -35,13 +38,73 @@ protected StepPhase executeAsyncStep(ProcessContext context) { CloudTask taskToExecute = StepsUtil.getTask(context); CloudControllerClient client = context.getControllerClient(); - getStepLogger().info(Messages.EXECUTING_TASK_ON_APP, taskToExecute.getName(), app.getName()); - CloudTask startedTask = client.runTask(app.getName(), taskToExecute); + String appName = resolveTargetAppName(context, app); + + getStepLogger().info(Messages.EXECUTING_TASK_ON_APP, taskToExecute.getName(), appName); + CloudTask startedTask = client.runTask(appName, taskToExecute); context.setVariable(Variables.STARTED_TASK, startedTask); context.setVariable(Variables.START_TIME, currentTimeSupplier.getAsLong()); return StepPhase.POLL; } + private String resolveTargetAppName(ProcessContext context, CloudApplicationExtended app) { + Hook hook = context.getVariable(Variables.HOOK_FOR_EXECUTION); + if (hook == null || hook.getPhaseConfigs().isEmpty()) { + return app.getName(); + } + + String currentPhase = buildCurrentPhaseString(context, hook); + String targetApp = hook.getPhaseConfigs() + .stream() + .filter(config -> currentPhase.equals(config.get("phase"))) + .map(config -> config.get("target-app")) + .findFirst() + .orElse(null); + + if (targetApp == null) { + return app.getName(); + } + + return resolveAppNameForTarget(context, app, targetApp); + } + + private String buildCurrentPhaseString(ProcessContext context, Hook hook) { + // The hook's own phase string already contains the correct deployment type prefix. + // Use it directly rather than reconstructing from context variables that may be + // unavailable in the hook subprocess (e.g. SERVICE_ID absent → null process type). + return hook.getPhases() + .stream() + .filter(p -> p.contains(".application.")) + .findFirst() + .orElse(""); + } + + private String resolveAppNameForTarget(ProcessContext context, CloudApplicationExtended app, String targetApp) { + ApplicationColor idleColor = context.getVariable(Variables.IDLE_MTA_COLOR); + ApplicationColor liveColor = context.getVariable(Variables.LIVE_MTA_COLOR); + + if (idleColor == null || liveColor == null) { + return app.getName(); + } + + if (HookPhaseProcessType.HookProcessPhase.IDLE.getType().equals(targetApp)) { + return swapColorSuffix(app.getName(), liveColor, idleColor); + } + if (HookPhaseProcessType.HookProcessPhase.LIVE.getType().equals(targetApp)) { + return swapColorSuffix(app.getName(), idleColor, liveColor); + } + return app.getName(); + } + + private String swapColorSuffix(String appName, ApplicationColor fromColor, ApplicationColor toColor) { + String fromSuffix = fromColor.asSuffix(); + String toSuffix = toColor.asSuffix(); + if (appName.endsWith(fromSuffix)) { + return appName.substring(0, appName.length() - fromSuffix.length()) + toSuffix; + } + return appName; + } + @Override protected String getStepErrorMessage(ProcessContext context) { CloudApplicationExtended app = context.getVariable(Variables.APP_TO_PROCESS); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/DeploymentTypeDeterminer.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/DeploymentTypeDeterminer.java index f4889b1c3c..c4cf10fe61 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/DeploymentTypeDeterminer.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/DeploymentTypeDeterminer.java @@ -20,4 +20,8 @@ public ProcessType determineDeploymentType(ProcessContext context) { return processTypeParser.getProcessType(context.getExecution()); } + public ProcessType determineDeploymentTypeSafely(ProcessContext context) { + return processTypeParser.getProcessType(context.getExecution(), false); + } + } diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn index e9522fa8ff..f43ecd459b 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn @@ -24,6 +24,10 @@ + + + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn index 5015ce6d84..cc38abce03 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn @@ -115,6 +115,10 @@ + + + + @@ -143,6 +147,10 @@ + + + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn index f6dfd51040..c2f200a6f0 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn @@ -57,6 +57,9 @@ + + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn index 5062e65228..f69fc3daf1 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn @@ -25,6 +25,10 @@ + + + + @@ -55,6 +59,10 @@ + + + + diff --git a/pom.xml b/pom.xml index 093fbc3a63..ee8a5fd2c3 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ 1.1.1 1.21.0 - 2.46.0 + 2.47.0-SNAPSHOT 3.5.1 4.0.5 4.0.6 From 36e6fc913489601c3cd6869857a0a0ca6cbe5626 Mon Sep 17 00:00:00 2001 From: stiv03 Date: Fri, 3 Apr 2026 10:56:57 +0300 Subject: [PATCH 2/3] Fix phase-configs target-app resolution for multi-phase hooks buildCurrentPhaseString was always picking the first phase from hook.getPhases(), causing wrong target-app resolution when a hook registered for multiple phases fired during a later phase. Also fixed getDeploymentTypeString returning "deploy" instead of "blue-green" inside hook subprocesses where SERVICE_ID is absent. Introduced HOOK_EXECUTION_PHASE variable set by HooksExecutor to record which phase triggered the current hook execution. ExecuteTaskStep now matches against this value to find the correct phase-config entry. Variable is passed to all hook call activities in deploy-app, undeploy-app, backup-existing-app and stop-dependent-modules BPMNs. --- .../controller/process/steps/ExecuteTaskStep.java | 12 +++++++----- .../controller/process/util/HooksExecutor.java | 5 +++++ .../controller/process/variables/Variables.java | 3 +++ .../controller/process/backup-existing-app.bpmn | 3 ++- .../multiapps/controller/process/deploy-app.bpmn | 2 ++ .../controller/process/stop-dependent-modules.bpmn | 1 + .../multiapps/controller/process/undeploy-app.bpmn | 2 ++ pom.xml | 2 +- 8 files changed, 23 insertions(+), 7 deletions(-) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java index a333517d90..69dc593d43 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java @@ -69,14 +69,16 @@ private String resolveTargetAppName(ProcessContext context, CloudApplicationExte } private String buildCurrentPhaseString(ProcessContext context, Hook hook) { - // The hook's own phase string already contains the correct deployment type prefix. - // Use it directly rather than reconstructing from context variables that may be - // unavailable in the hook subprocess (e.g. SERVICE_ID absent → null process type). + String hookExecutionPhase = context.getVariable(Variables.HOOK_EXECUTION_PHASE); return hook.getPhases() .stream() - .filter(p -> p.contains(".application.")) + .filter(p -> p.equals(hookExecutionPhase)) .findFirst() - .orElse(""); + .orElseGet(() -> hook.getPhases() + .stream() + .filter(p -> p.contains(".application.")) + .findFirst() + .orElse("")); } private String resolveAppNameForTarget(ProcessContext context, CloudApplicationExtended app, String targetApp) { diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/HooksExecutor.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/HooksExecutor.java index 001209579d..8ccf349c28 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/HooksExecutor.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/HooksExecutor.java @@ -50,6 +50,11 @@ private List executeHooks(StepPhase currentStepPhase) { moduleToDeploy.getName()); updateExecutedHooksForModule(alreadyExecutedHooksForModule, hooksWithPhases.getHookPhases(), hooksWithPhases.getHooks()); context.setVariable(Variables.HOOKS_FOR_EXECUTION, hooksWithPhases.getHooks()); + context.setVariable(Variables.HOOK_EXECUTION_PHASE, hooksWithPhases.getHookPhases() + .stream() + .findFirst() + .map(HookPhase::getValue) + .orElse(null)); return hooksWithPhases.getHooks(); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java index 8c35932c48..ff895a7232 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java @@ -580,6 +580,9 @@ public interface Variables { .type(Variable.typeReference(Hook.class)) .defaultValue(Collections.emptyList()) .build(); + Variable HOOK_EXECUTION_PHASE = ImmutableSimpleVariable. builder() + .name("hookExecutionPhase") + .build(); Variable> MODULES_TO_DEPLOY = ImmutableJsonBinaryListVariable. builder() .name("modulesToDeploy") .type(Variable.typeReference(Module.class)) diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn index f43ecd459b..d9724a2826 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn @@ -26,7 +26,8 @@ - + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn index cc38abce03..bae2ad3664 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn @@ -117,6 +117,7 @@ + @@ -149,6 +150,7 @@ + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn index c2f200a6f0..68ffddb76e 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn @@ -58,6 +58,7 @@ + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn index f69fc3daf1..e431438330 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn @@ -27,6 +27,7 @@ + @@ -61,6 +62,7 @@ + diff --git a/pom.xml b/pom.xml index ee8a5fd2c3..093fbc3a63 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ 1.1.1 1.21.0 - 2.47.0-SNAPSHOT + 2.46.0 3.5.1 4.0.5 4.0.6 From f82d85dd985e76b4f3a78cd6a238e89e8cca5726 Mon Sep 17 00:00:00 2001 From: stiv03 Date: Wed, 8 Apr 2026 15:05:03 +0300 Subject: [PATCH 3/3] Fix phase-configs target-app routing when live app has no color suffix --- .../multiapps/controller/process/steps/ExecuteTaskStep.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java index 69dc593d43..38de854e12 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExecuteTaskStep.java @@ -104,6 +104,9 @@ private String swapColorSuffix(String appName, ApplicationColor fromColor, Appli if (appName.endsWith(fromSuffix)) { return appName.substring(0, appName.length() - fromSuffix.length()) + toSuffix; } + if (!appName.endsWith(toSuffix)) { + return appName + toSuffix; + } return appName; }