Skip to content

Add phase-configs support to Hook model for target-app resolution#1812

Open
stiv03 wants to merge 12 commits into
cloudfoundry:masterfrom
stiv03:feature/hook-phase-target-app
Open

Add phase-configs support to Hook model for target-app resolution#1812
stiv03 wants to merge 12 commits into
cloudfoundry:masterfrom
stiv03:feature/hook-phase-target-app

Conversation

@stiv03
Copy link
Copy Markdown
Contributor

@stiv03 stiv03 commented Apr 3, 2026

No description provided.

@stiv03 stiv03 marked this pull request as draft April 3, 2026 08:17
@stiv03 stiv03 marked this pull request as ready for review April 8, 2026 12:08
@stiv03 stiv03 force-pushed the feature/hook-phase-target-app branch from 20deee7 to 0827e89 Compare April 20, 2026 08:15
stiv03 added 4 commits April 20, 2026 11:22
…reen 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.

# Conflicts:
#	pom.xml
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.
@stiv03 stiv03 force-pushed the feature/hook-phase-target-app branch from 0827e89 to c5bab3b Compare April 20, 2026 08:26
@stiv03 stiv03 force-pushed the feature/hook-phase-target-app branch from c5bab3b to 9165d29 Compare April 20, 2026 08:30
@stiv03 stiv03 force-pushed the feature/hook-phase-target-app branch from 13a5600 to 7deb07c Compare May 7, 2026 07:43
Comment on lines +73 to +89
void appNameUnchangedWhenNoPhasesConfigMatchesCurrentPhase() {
context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_NAME_LIVE));
context.setVariable(Variables.HOOK_EXECUTION_PHASES, List.of(BEFORE_STOP_LIVE));
context.setVariable(Variables.HOOK_FOR_EXECUTION, buildHookWithPhasesConfig(
List.of(BEFORE_STOP_LIVE),
List.of(Map.of(PHASE_KEY, BEFORE_START_IDLE, TARGET_APP_KEY, TARGET_IDLE))
));

step.execute(execution);

assertStepFinishedSuccessfully();
assertEquals(APP_NAME_LIVE, context.getVariable(Variables.APP_TO_PROCESS)
.getName());
}

@Test
void bgDeploy_targetIdle_resolvesToIdleColorApp() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stick to one naming convention for the test method names

public static final String CANNOT_RETRIEVE_PARAMETERS_OF_BINDING_BETWEEN_APPLICATION_0_AND_SERVICE_INSTANCE_1 = "Cannot retrieve parameters of binding between application \"{0}\" and service instance \"{1}\"";
public static final String CANNOT_RETRIEVE_PARAMETERS_OF_BINDING_BETWEEN_APPLICATION_0_AND_SERVICE_INSTANCE_1_FIX = "Cannot retrieve parameters of binding between application \"{0}\" and service instance \"{1}\". Got 502.";
public static final String CANNOT_RETRIEVE_INSTANCE_OF_SERVICE = "Cannot retrieve service instance of service \"{0}\"";
public static final String HOOK_PHASE_BLUE_GREEN_BEFORE_UNMAP_ROUTES_IDLE_USED = "Hook \"{0}\" uses deprecated phase \"blue-green.application.before-unmap-routes.idle\" which is unreachable and will be removed in a future version";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log message flows a bit better to me:
Hook \"{0}\" uses the deprecated phase \"blue-green.application.before-unmap-routes.idle\". This phase is unreachable and will be removed in a future version.

SERVICE_BINDING_CONFIG, DELETE_SERVICE_KEY_AFTER_DEPLOYMENT);

public static final Set<String> MODULE_HOOK_PARAMETERS = Set.of(NAME, COMMAND, MEMORY, DISK_QUOTA, HOOK_REQUIRES);
public static final String PHASES_CONFIG = "phases-config";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move PHASES_CONFIG up with the other public static final declarations.

Comment on lines +43 to +52
@SuppressWarnings("unchecked")
private void validateHookHasNoDuplicatePhaseConfigs(Hook hook) {
Object phasesConfigValue = hook.getParameters().get(SupportedParameters.PHASES_CONFIG);
if (phasesConfigValue == null) {
return;
}
if (!(phasesConfigValue instanceof List)) {
throw new SLException(MessageFormat.format(Messages.INVALID_PHASES_CONFIG_NOT_A_LIST, hook.getName()));
}
List<Map<String, String>> phasesConfig = (List<Map<String, String>>) phasesConfigValue;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we scope @SuppressWarnings("unchecked") to just the cast instead of the whole method?

Comment on lines +68 to +76
@SuppressWarnings("unchecked")
private List<Map<String, String>> getPhasesConfig(Hook hook) {
Object phasesConfigValue = hook.getParameters()
.get(SupportedParameters.PHASES_CONFIG);
if (phasesConfigValue instanceof List) {
return (List<Map<String, String>>) phasesConfigValue;
}
return List.of();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We treat missing or null PHASES_CONFIG as empty, but silently ignore cases where it’s present with the wrong type. Would it make sense to log or validate that here?

Comment on lines +43 to +66
private String resolveTargetAppName(ProcessContext context, CloudApplicationExtended app) {
Hook hook = context.getVariable(Variables.HOOK_FOR_EXECUTION);
if (hook == null) {
return app.getName();
}

List<Map<String, String>> phasesConfig = getPhasesConfig(hook);
if (phasesConfig.isEmpty()) {
return app.getName();
}

String currentPhase = buildCurrentPhaseString(context, hook);
String targetApp = phasesConfig.stream()
.filter(config -> currentPhase.equals(config.get(PHASE_KEY)))
.map(config -> config.get(TARGET_APP_KEY))
.findFirst()
.orElse(null);

if (targetApp == null) {
return app.getName();
}

return resolveAppNameForTarget(context, app, targetApp);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small refactoring suggestion: app.getName() is returned in multiple branches here. Consider assigning it as the default result and only changing it when a target app is found, to reduce repetition.

Comment on lines +32 to +41
if (resolvedAppName == null) {
context.setVariable(Variables.TASKS_TO_EXECUTE, List.of());
return StepPhase.DONE;
}
if (!resolvedAppName.equals(app.getName())) {
context.setVariable(Variables.APP_TO_PROCESS, buildAppWithName(app, resolvedAppName));
}

return StepPhase.DONE;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here all branches return StepPhase.DONE. Would it be clearer to return once at the end and update the context conditionally (e.g., using an else if)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants