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..5e9c684f8d 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,10 @@ 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 String PHASES_CONFIG = "phases-config"; + + public static final Set MODULE_HOOK_PARAMETERS = Set.of(NAME, COMMAND, MEMORY, DISK_QUOTA, HOOK_REQUIRES, HOOK_TARGET_APP, PHASES_CONFIG); 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/Messages.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java index e06960b330..79b4700e73 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java @@ -149,6 +149,8 @@ public class Messages { public static final String ERROR_PREPARING_TO_EXECUTE_TASKS_ON_APP = "Error preparing to execute tasks on application \"{0}\""; public static final String ERROR_PREPARING_TO_RESTART_SERVICE_BROKER_SUBSCRIBERS = "Error preparing to restart service broker subscribers"; public static final String ERROR_EXECUTING_TASK_0_ON_APP_1 = "Execution of task \"{0}\" on application \"{1}\" failed."; + public static final String DUPLICATE_PHASE_IN_PHASES_CONFIG = "Duplicate phase \"{0}\" in \"phases-config\" of hook \"{1}\". Only one target-app per phase is supported."; + public static final String INVALID_PHASES_CONFIG_NOT_A_LIST = "Parameter \"phases-config\" of hook \"{0}\" must be a list."; public static final String ERROR_DETECTING_COMPONENTS_TO_UNDEPLOY = "Error detecting components to undeploy"; public static final String ERROR_DELETING_IDLE_ROUTES = "Error deleting idle routes"; public static final String ERROR_CREATING_SERVICE_BROKERS = "Error creating service brokers"; @@ -409,6 +411,7 @@ public class Messages { public static final String WILL_NOT_REBIND_APP_TO_SERVICE_SAME_PARAMETERS = "Service instance \"{0}\" will not be rebound to application \"{1}\" because the binding parameters are not modified"; public static final String SERVICE_BROKER_DOES_NOT_EXIST = "Service broker with name \"{0}\" does not exist"; public static final String EXECUTING_HOOK_0 = "Executing hook \"{0}\""; + public static final String SKIPPING_HOOK_TASK_NO_LIVE_APP = "Skipping hook task on application \"{0}\" with target-app \"live\": no live application exists yet (initial deployment)"; public static final String WAITING_PREVIOUS_OPERATIONS_TO_FINISH = "Waiting for previous service operations to finish..."; public static final String ASYNC_OPERATION_FOR_SERVICE_BROKER_FINISHED = "Async operation for service broker \"{0}\" has finished"; public static final String STARTING_INCREMENTAL_APPLICATION_INSTANCE_UPDATE_FOR_0 = "Starting incremental application instance update for \"{0}\"..."; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStep.java index aeba1c87fa..616573986b 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStep.java @@ -15,6 +15,7 @@ import org.cloudfoundry.multiapps.controller.persistence.services.DescriptorBackupService; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.security.SecretParametersCollector; +import org.cloudfoundry.multiapps.controller.process.util.HookPhasesConfigValidator; import org.cloudfoundry.multiapps.controller.process.util.NamespaceGlobalParameters; import org.cloudfoundry.multiapps.controller.process.util.UnsupportedParameterFinder; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -30,6 +31,8 @@ @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class MergeDescriptorsStep extends SyncFlowableStep { + private static final int HOOKS_MIN_SCHEMA_VERSION = 3; + @Inject private DescriptorBackupService descriptorBackupService; @@ -59,6 +62,9 @@ protected StepPhase executeStep(ProcessContext context) { .toList()); context.setVariable(Variables.DEPLOYMENT_DESCRIPTOR, descriptor); + if (context.getVariable(Variables.MTA_MAJOR_SCHEMA_VERSION) >= HOOKS_MIN_SCHEMA_VERSION) { + new HookPhasesConfigValidator().validate(descriptor); + } warnForUnsupportedParameters(descriptor); backupDeploymentDescriptor(context, descriptor); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ResolveHookTaskTargetAppStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ResolveHookTaskTargetAppStep.java new file mode 100644 index 0000000000..a34126acbb --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ResolveHookTaskTargetAppStep.java @@ -0,0 +1,132 @@ +package org.cloudfoundry.multiapps.controller.process.steps; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Named; +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; +import org.cloudfoundry.multiapps.controller.core.model.ApplicationColor; +import org.cloudfoundry.multiapps.controller.core.model.BlueGreenApplicationNameSuffix; +import org.cloudfoundry.multiapps.controller.core.model.HookPhaseProcessType; +import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; +import org.cloudfoundry.multiapps.controller.process.Messages; +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; + +@Named("resolveHookTaskTargetAppStep") +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class ResolveHookTaskTargetAppStep extends SyncFlowableStep { + + private static final String PHASE_KEY = "phase"; + private static final String TARGET_APP_KEY = "target-app"; + + @Override + protected StepPhase executeStep(ProcessContext context) { + CloudApplicationExtended app = context.getVariable(Variables.APP_TO_PROCESS); + + String resolvedAppName = resolveTargetAppName(context, app); + 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; + } + + private String resolveTargetAppName(ProcessContext context, CloudApplicationExtended app) { + Hook hook = context.getVariable(Variables.HOOK_FOR_EXECUTION); + if (hook == null) { + return app.getName(); + } + + List> 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); + } + + @SuppressWarnings("unchecked") + private List> getPhasesConfig(Hook hook) { + Object phasesConfigValue = hook.getParameters() + .get(SupportedParameters.PHASES_CONFIG); + if (phasesConfigValue instanceof List) { + return (List>) phasesConfigValue; + } + return List.of(); + } + + private String buildCurrentPhaseString(ProcessContext context, Hook hook) { + List hookExecutionPhases = context.getVariable(Variables.HOOK_EXECUTION_PHASES); + return hook.getPhases() + .stream() + .filter(hookExecutionPhases::contains) + .findFirst() + .orElse(""); + } + + private String resolveAppNameForTarget(ProcessContext context, CloudApplicationExtended app, String targetApp) { + if (HookPhaseProcessType.HookProcessPhase.LIVE.getType() + .equals(targetApp)) { + if (isInitialDeploy(context)) { + getStepLogger().warn(Messages.SKIPPING_HOOK_TASK_NO_LIVE_APP, app.getName()); + return null; + } + ApplicationColor liveColor = context.getVariable(Variables.LIVE_MTA_COLOR); + String suffix = liveColor != null ? liveColor.asSuffix() : BlueGreenApplicationNameSuffix.LIVE.asSuffix(); + return BlueGreenApplicationNameSuffix.removeSuffix(app.getName()) + suffix; + } + if (HookPhaseProcessType.HookProcessPhase.IDLE.getType() + .equals(targetApp)) { + String baseName = BlueGreenApplicationNameSuffix.removeSuffix(app.getName()); + ApplicationColor idleColor = context.getVariable(Variables.IDLE_MTA_COLOR); + if (idleColor != null) { + return baseName + idleColor.asSuffix(); + } + if (isAfterRenamePhase(context)) { + return baseName; + } + return baseName + BlueGreenApplicationNameSuffix.IDLE.asSuffix(); + } + return app.getName(); + } + + private boolean isAfterRenamePhase(ProcessContext context) { + return context.getVariable(Variables.PHASE) != null; + } + + private boolean isInitialDeploy(ProcessContext context) { + return context.getVariable(Variables.DEPLOYED_MTA) == null; + } + + private CloudApplicationExtended buildAppWithName(CloudApplicationExtended app, String resolvedAppName) { + return org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended.copyOf(app) + .withName(resolvedAppName); + } + + @Override + protected String getStepErrorMessage(ProcessContext context) { + return MessageFormat.format(Messages.ERROR_EXECUTING_HOOK, + context.getVariable(Variables.HOOK_FOR_EXECUTION) + .getName()); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/HookPhasesConfigValidator.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/HookPhasesConfigValidator.java new file mode 100644 index 0000000000..5957b7df21 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/HookPhasesConfigValidator.java @@ -0,0 +1,45 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.text.MessageFormat; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.cloudfoundry.multiapps.common.SLException; +import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; +import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.cloudfoundry.multiapps.mta.model.Hook; + +public class HookPhasesConfigValidator { + + private static final String PHASE_KEY = "phase"; + + public void validate(DeploymentDescriptor descriptor) { + descriptor.getModules() + .stream() + .flatMap(module -> module.getHooks().stream()) + .forEach(this::validateHookHasNoDuplicatePhaseConfigs); + } + + @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> phasesConfig = (List>) phasesConfigValue; + Set seenPhases = new HashSet<>(); + for (Map phaseConfig : phasesConfig) { + String phase = phaseConfig.get(PHASE_KEY); + if (phase != null && !seenPhases.add(phase)) { + throw new SLException(MessageFormat.format(Messages.DUPLICATE_PHASE_IN_PHASES_CONFIG, phase, hook.getName())); + } + } + } + +} 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..ffc393a143 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,10 @@ 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_PHASES, hooksWithPhases.getHookPhases() + .stream() + .map(HookPhase::getValue) + .toList()); 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..043ee1a1d4 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,10 @@ public interface Variables { .type(Variable.typeReference(Hook.class)) .defaultValue(Collections.emptyList()) .build(); + Variable> HOOK_EXECUTION_PHASES = ImmutableSimpleVariable.> builder() + .name("hookExecutionPhases") + .defaultValue(Collections.emptyList()) + .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 e9522fa8ff..ecbecf7e21 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,11 @@ + + + + + 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..895d0f0fc9 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,11 @@ + + + + + @@ -143,6 +148,11 @@ + + + + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/execute-hook-tasks.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/execute-hook-tasks.bpmn index 4408c082ff..927add0de7 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/execute-hook-tasks.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/execute-hook-tasks.bpmn @@ -9,13 +9,15 @@ + - + + 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..f4f5296b52 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,10 @@ + + + + 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..1e5e5eb9cb 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,11 @@ + + + + + @@ -55,6 +60,11 @@ + + + + + diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ResolveHookTaskTargetAppStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ResolveHookTaskTargetAppStepTest.java new file mode 100644 index 0000000000..915ab970cd --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ResolveHookTaskTargetAppStepTest.java @@ -0,0 +1,276 @@ +package org.cloudfoundry.multiapps.controller.process.steps; + +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; +import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; +import org.cloudfoundry.multiapps.controller.core.model.ApplicationColor; +import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; +import org.cloudfoundry.multiapps.controller.core.model.ImmutableDeployedMta; +import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; +import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.mta.model.Hook; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ResolveHookTaskTargetAppStepTest extends SyncFlowableStepTest { + + private static final String BEFORE_STOP_LIVE = "blue-green.application.before-stop.live"; + private static final String BEFORE_START_IDLE = "blue-green.application.before-start.idle"; + + private static final String PHASE_KEY = "phase"; + private static final String TARGET_APP_KEY = "target-app"; + private static final String TARGET_IDLE = "idle"; + private static final String TARGET_LIVE = "live"; + + private static final String HOOK_NAME = "test-hook"; + private static final String APP_BASE_NAME = "my-app"; + private static final String APP_NAME_GREEN = "my-app-green"; + private static final String APP_NAME_BLUE = "my-app-blue"; + private static final String APP_NAME_LIVE = "my-app-live"; + private static final String APP_NAME_IDLE = "my-app-idle"; + + private static final DeployedMta EXISTING_MTA = ImmutableDeployedMta.builder() + .metadata(ImmutableMtaMetadata.builder() + .id("test-mta") + .build()) + .build(); + + @Override + protected ResolveHookTaskTargetAppStep createStep() { + return new ResolveHookTaskTargetAppStep(); + } + + @Test + void appNameUnchangedWhenNoHookSet() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_BASE_NAME)); + context.setVariable(Variables.HOOK_FOR_EXECUTION, null); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_BASE_NAME, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void appNameUnchangedWhenHookHasNoPhasesConfig() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_BASE_NAME)); + context.setVariable(Variables.HOOK_FOR_EXECUTION, buildHook(List.of(BEFORE_STOP_LIVE), Map.of())); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_BASE_NAME, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + 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() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_NAME_GREEN)); + context.setVariable(Variables.IDLE_MTA_COLOR, ApplicationColor.BLUE); + context.setVariable(Variables.LIVE_MTA_COLOR, ApplicationColor.GREEN); + 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_STOP_LIVE, TARGET_APP_KEY, TARGET_IDLE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_BLUE, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void bgDeploy_targetLive_resolvesToLiveColorApp() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_NAME_BLUE)); + context.setVariable(Variables.IDLE_MTA_COLOR, ApplicationColor.BLUE); + context.setVariable(Variables.LIVE_MTA_COLOR, ApplicationColor.GREEN); + context.setVariable(Variables.DEPLOYED_MTA, EXISTING_MTA); + context.setVariable(Variables.HOOK_EXECUTION_PHASES, List.of(BEFORE_START_IDLE)); + context.setVariable(Variables.HOOK_FOR_EXECUTION, buildHookWithPhasesConfig( + List.of(BEFORE_START_IDLE), + List.of(Map.of(PHASE_KEY, BEFORE_START_IDLE, TARGET_APP_KEY, TARGET_LIVE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_GREEN, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void bgDeploy_targetIdle_appHasNoSuffix_appendsIdleColorSuffix() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_BASE_NAME)); + context.setVariable(Variables.IDLE_MTA_COLOR, ApplicationColor.BLUE); + context.setVariable(Variables.LIVE_MTA_COLOR, ApplicationColor.GREEN); + 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_STOP_LIVE, TARGET_APP_KEY, TARGET_IDLE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_BLUE, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void strategyBgDeploy_targetIdle_appHasLiveSuffix_resolvesToIdleApp() { + 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_STOP_LIVE, TARGET_APP_KEY, TARGET_IDLE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_IDLE, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void strategyBgDeploy_targetIdle_appHasNoSuffix_appendsIdleSuffix() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_BASE_NAME)); + 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_STOP_LIVE, TARGET_APP_KEY, TARGET_IDLE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_IDLE, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void strategyBgDeploy_targetIdle_appAlreadyHasIdleSuffix_unchanged() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_NAME_IDLE)); + 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_STOP_LIVE, TARGET_APP_KEY, TARGET_IDLE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_IDLE, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void strategyBgDeploy_targetLive_appHasIdleSuffix_resolvesToLiveApp() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_NAME_IDLE)); + context.setVariable(Variables.DEPLOYED_MTA, EXISTING_MTA); + context.setVariable(Variables.HOOK_EXECUTION_PHASES, List.of(BEFORE_START_IDLE)); + context.setVariable(Variables.HOOK_FOR_EXECUTION, buildHookWithPhasesConfig( + List.of(BEFORE_START_IDLE), + List.of(Map.of(PHASE_KEY, BEFORE_START_IDLE, TARGET_APP_KEY, TARGET_LIVE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_LIVE, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void strategyBgDeploy_targetLive_appAlreadyHasLiveSuffix_unchanged() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_NAME_LIVE)); + context.setVariable(Variables.DEPLOYED_MTA, EXISTING_MTA); + context.setVariable(Variables.HOOK_EXECUTION_PHASES, List.of(BEFORE_START_IDLE)); + context.setVariable(Variables.HOOK_FOR_EXECUTION, buildHookWithPhasesConfig( + List.of(BEFORE_START_IDLE), + List.of(Map.of(PHASE_KEY, BEFORE_START_IDLE, TARGET_APP_KEY, TARGET_LIVE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_LIVE, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void strategyBgDeploy_targetLive_appHasNoSuffix_appendsLiveSuffix() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_BASE_NAME)); + context.setVariable(Variables.DEPLOYED_MTA, EXISTING_MTA); + context.setVariable(Variables.HOOK_EXECUTION_PHASES, List.of(BEFORE_START_IDLE)); + context.setVariable(Variables.HOOK_FOR_EXECUTION, buildHookWithPhasesConfig( + List.of(BEFORE_START_IDLE), + List.of(Map.of(PHASE_KEY, BEFORE_START_IDLE, TARGET_APP_KEY, TARGET_LIVE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertEquals(APP_NAME_LIVE, context.getVariable(Variables.APP_TO_PROCESS) + .getName()); + } + + @Test + void initialDeploy_targetLive_skipsTask() { + context.setVariable(Variables.APP_TO_PROCESS, buildApp(APP_NAME_IDLE)); + context.setVariable(Variables.HOOK_EXECUTION_PHASES, List.of(BEFORE_START_IDLE)); + context.setVariable(Variables.HOOK_FOR_EXECUTION, buildHookWithPhasesConfig( + List.of(BEFORE_START_IDLE), + List.of(Map.of(PHASE_KEY, BEFORE_START_IDLE, TARGET_APP_KEY, TARGET_LIVE)) + )); + + step.execute(execution); + + assertStepFinishedSuccessfully(); + assertTrue(context.getVariable(Variables.TASKS_TO_EXECUTE).isEmpty()); + } + + private CloudApplicationExtended buildApp(String name) { + return ImmutableCloudApplicationExtended.builder() + .name(name) + .build(); + } + + private Hook buildHook(List phases, Map parameters) { + return Hook.createV3() + .setName(HOOK_NAME) + .setType("task") + .setPhases(phases) + .setParameters(parameters); + } + + private Hook buildHookWithPhasesConfig(List phases, + List> phasesConfig) { + return buildHook(phases, Map.of(SupportedParameters.PHASES_CONFIG, phasesConfig)); + } + +}