From 37f2d28b99f32cd1de782e67f0af7da318179983 Mon Sep 17 00:00:00 2001 From: vkalapov <87693906+vkalapov@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:56:50 +0300 Subject: [PATCH 1/2] Add --dry-run flag with DryRunCloudControllerClient and diff output --- .../process/client/DryRunChangeSet.java | 19 + .../client/DryRunCloudControllerClient.java | 772 ++++++++++++++++++ .../process/metadata/DeployMetadata.java | 5 + .../process/steps/AsyncFlowableStep.java | 3 + .../CheckServiceBindingOperationStep.java | 3 + .../CollectServicesInProgressStateStep.java | 3 + .../process/steps/CreateOrUpdateAppStep.java | 14 + ...eApplicationServiceBindingActionsStep.java | 5 + ...rmineDesiredStateAchievingActionsStep.java | 4 + ...mineServiceDeleteActionsToExecuteStep.java | 7 + .../process/steps/ProcessContext.java | 12 +- .../PublishConfigurationEntriesStep.java | 18 + .../process/steps/ScaleAppStep.java | 3 + .../controller/process/steps/ServiceStep.java | 3 + .../process/steps/UploadAppStep.java | 3 + .../process/variables/Variables.java | 21 + .../controller/process/deploy-app.bpmn | 6 + .../process/process-batches-sequentially.bpmn | 1 + .../controller/process/xs2-deploy.bpmn | 10 + .../process/metadata/DeployMetadataTest.java | 1 + .../process/steps/deployed-mta-01.json | 3 + .../process/steps/deployed-mta-02.json | 1 + .../process/steps/deployed-mta-03.json | 15 +- .../process/steps/deployed-mta-04.json | 6 +- .../process/steps/deployed-mta-05.json | 15 +- .../process/steps/deployed-mta-06.json | 6 +- .../process/steps/deployed-mta-07.json | 9 +- .../process/steps/deployed-mta-08.json | 15 +- .../process/steps/deployed-mta-09.json | 6 +- .../process/steps/deployed-mta-10.json | 6 +- .../process/steps/deployed-mta-12.json | 9 +- .../process/steps/deployed-mta-13.json | 9 +- 32 files changed, 979 insertions(+), 34 deletions(-) create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/DryRunChangeSet.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/DryRunCloudControllerClient.java diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/DryRunChangeSet.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/DryRunChangeSet.java new file mode 100644 index 0000000000..bf64ed2e9e --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/DryRunChangeSet.java @@ -0,0 +1,19 @@ +package org.cloudfoundry.multiapps.controller.process.client; + +import java.util.ArrayList; +import java.util.List; + +public class DryRunChangeSet { + + public List appsCreate = new ArrayList<>(); + public List appsUpdate = new ArrayList<>(); + public List appsDelete = new ArrayList<>(); + public List servicesCreate = new ArrayList<>(); + public List servicesUpdate = new ArrayList<>(); + public List servicesDelete = new ArrayList<>(); + public List routesCreate = new ArrayList<>(); + public List routesDelete = new ArrayList<>(); + + public DryRunChangeSet() { + } +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/DryRunCloudControllerClient.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/DryRunCloudControllerClient.java new file mode 100644 index 0000000000..47a4a8097a --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/DryRunCloudControllerClient.java @@ -0,0 +1,772 @@ +package org.cloudfoundry.multiapps.controller.process.client; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; + +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.ApplicationServicesUpdateCallback; +import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; +import org.cloudfoundry.multiapps.controller.client.facade.ServiceBindingOperationCallback; +import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; + +/** + * THROWAWAY SPIKE. Dry-run decorator over {@link CloudControllerClient}: every read forwards to the + * delegate; every mutation is recorded into the {@link DryRunChangeSet} process variable and NOT + * executed against CF. Some stub returns are null and get patched reactively (plan corners #2/#3). + */ +public class DryRunCloudControllerClient implements CloudControllerClient { + + private final CloudControllerClient delegate; + private final ProcessContext context; + // Tracks app names "created" during this dry run so reads can return a stub instead of hitting CF + private final Set createdAppNames = new HashSet<>(); + + public DryRunCloudControllerClient(CloudControllerClient delegate, ProcessContext context) { + this.delegate = delegate; + this.context = context; + } + + private void record(Function> bucket, String entry) { + DryRunChangeSet cs = context.getVariable(Variables.DRY_RUN_CHANGE_SET); + if (cs == null) { + cs = new DryRunChangeSet(); + } + List list = bucket.apply(cs); + if (!list.contains(entry)) { + list.add(entry); + } + context.setVariable(Variables.DRY_RUN_CHANGE_SET, cs); + } + + private CloudApplication stubApp(String name) { + return ImmutableCloudApplication.builder() + .name(name) + .metadata(ImmutableCloudMetadata.of(UUID.randomUUID())) + .build(); + } + + @Override + public CloudSpace getTarget() + { + return delegate.getTarget(); + } + + @Override + public void addDomain(String domainName) + { + record(cs -> cs.routesCreate, domainName); + } + + @Override + public void addRoute(String host, String domainName, String path) + { + record(cs -> cs.routesCreate, host + "." + domainName); + } + + @Override + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) + { + record(cs -> cs.servicesCreate, serviceInstanceName); + return Optional.empty(); + } + + @Override + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, + Map parameters, ApplicationServicesUpdateCallback updateServicesCallback) + { + record(cs -> cs.servicesCreate, serviceInstanceName); + return Optional.empty(); + } + + @Override + public void createApplication(ApplicationToCreateDto applicationToCreateDto) + { + record(cs -> cs.appsCreate, applicationToCreateDto.getName()); + createdAppNames.add(applicationToCreateDto.getName()); + } + + @Override + public void createServiceInstance(CloudServiceInstance serviceInstance) + { + record(cs -> cs.servicesCreate, serviceInstance.getName()); + } + + @Override + public String createServiceBroker(CloudServiceBroker serviceBroker) + { + record(cs -> cs.servicesCreate, serviceBroker.getName()); + return "dry-run-job-id"; + } + + @Override + public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String serviceInstanceName) + { + record(cs -> cs.servicesCreate, serviceInstanceName); + return null; + } + + @Override + public Optional createServiceKey(CloudServiceKey keyModel, String serviceInstanceName) + { + record(cs -> cs.servicesCreate, serviceInstanceName); + return Optional.empty(); + } + + @Override + public Optional createServiceKey(String serviceInstanceName, String serviceKeyName, Map parameters) + { + record(cs -> cs.servicesCreate, serviceInstanceName); + return Optional.empty(); + } + + @Override + public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) + { + record(cs -> cs.servicesCreate, serviceInstance.getName()); + } + + @Override + public void deleteApplication(String applicationName) + { + record(cs -> cs.appsDelete, applicationName); + } + + @Override + public void deleteDomain(String domainName) + { + record(cs -> cs.routesDelete, domainName); + } + + @Override + public void deleteOrphanedRoutes() + { + record(cs -> cs.routesDelete, "deleteOrphanedRoutes"); + } + + @Override + public void deleteRoute(String host, String domainName, String path) + { + record(cs -> cs.routesDelete, host + "." + domainName); + } + + @Override + public void deleteServiceInstance(String serviceInstanceName) + { + record(cs -> cs.servicesDelete, serviceInstanceName); + } + + @Override + public void deleteServiceInstance(CloudServiceInstance serviceInstance) + { + record(cs -> cs.servicesDelete, serviceInstance.getName()); + } + + @Override + public String deleteServiceBroker(String name) + { + record(cs -> cs.servicesDelete, name); + return "dry-run-job-id"; + } + + @Override + public CloudServiceBinding getServiceBinding(UUID serviceBindingId) + { + return delegate.getServiceBinding(serviceBindingId); + } + + @Override + public Optional deleteServiceBinding(String serviceInstanceName, String serviceKeyName) { + return Optional.empty(); + } + + @Override + public Optional deleteServiceBinding(UUID bindingGuid, ServiceBindingOperationCallback serviceBindingOperationCallback) { + return Optional.empty(); + } + + @Override + public Optional deleteServiceBinding(UUID bindingGuid) { + return Optional.empty(); + } + + @Override + public CloudApplication getApplication(String applicationName) + { + if (createdAppNames.contains(applicationName)) { + return stubApp(applicationName); + } + return delegate.getApplication(applicationName); + } + + @Override + public CloudApplication getApplication(String applicationName, boolean required) + { + if (createdAppNames.contains(applicationName)) { + return stubApp(applicationName); + } + return delegate.getApplication(applicationName, required); + } + + @Override + public UUID getApplicationGuid(String applicationName) + { + if (createdAppNames.contains(applicationName)) { + return stubApp(applicationName).getGuid(); + } + return delegate.getApplicationGuid(applicationName); + } + + @Override + public String getApplicationName(UUID uuid) + { + return delegate.getApplicationName(uuid); + } + + @Override + public Map getApplicationEnvironment(String applicationName) + { + if (createdAppNames.contains(applicationName)) { + return Map.of(); + } + return delegate.getApplicationEnvironment(applicationName); + } + + @Override + public Map getApplicationEnvironment(UUID applicationGuid) + { + return delegate.getApplicationEnvironment(applicationGuid); + } + + @Override + public List getApplicationEvents(String applicationName) + { + return delegate.getApplicationEvents(applicationName); + } + + @Override + public List getEventsByActee(UUID uuid) + { + return delegate.getEventsByActee(uuid); + } + + @Override + public InstancesInfo getApplicationInstances(CloudApplication app) + { + return delegate.getApplicationInstances(app); + } + + @Override + public InstancesInfo getApplicationInstances(UUID applicationGuid) + { + return delegate.getApplicationInstances(applicationGuid); + } + + @Override + public CloudProcess getApplicationProcess(UUID applicationGuid) + { + return delegate.getApplicationProcess(applicationGuid); + } + + @Override + public List getApplicationRoutes(UUID applicationGuid) + { + return delegate.getApplicationRoutes(applicationGuid); + } + + @Override + public boolean getApplicationSshEnabled(UUID applicationGuid) + { + return delegate.getApplicationSshEnabled(applicationGuid); + } + + @Override + public Map getApplicationFeatures(UUID applicationGuid) + { + return delegate.getApplicationFeatures(applicationGuid); + } + + @Override + public List getApplications() + { + return delegate.getApplications(); + } + + @Override + public CloudDomain getDefaultDomain() + { + return delegate.getDefaultDomain(); + } + + @Override + public List getDomains() + { + return delegate.getDomains(); + } + + @Override + public List getDomainsForOrganization() + { + return delegate.getDomainsForOrganization(); + } + + @Override + public List getEvents() + { + return delegate.getEvents(); + } + + @Override + public List getPrivateDomains() + { + return delegate.getPrivateDomains(); + } + + @Override + public List getRoutes(String domainName) + { + return delegate.getRoutes(domainName); + } + + @Override + public CloudServiceBroker getServiceBroker(String name) + { + return delegate.getServiceBroker(name); + } + + @Override + public CloudServiceBroker getServiceBroker(String name, boolean required) + { + return delegate.getServiceBroker(name, required); + } + + @Override + public List getServiceBrokers() + { + return delegate.getServiceBrokers(); + } + + @Override + public UUID getRequiredServiceInstanceGuid(String name) + { + return delegate.getRequiredServiceInstanceGuid(name); + } + + @Override + public CloudServiceInstance getServiceInstance(String serviceInstanceName) + { + return delegate.getServiceInstance(serviceInstanceName); + } + + @Override + public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) + { + return delegate.getServiceInstance(serviceInstanceName, required); + } + + @Override + public String getServiceInstanceName(UUID serviceInstanceGuid) + { + return delegate.getServiceInstanceName(serviceInstanceGuid); + } + + @Override + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName) + { + return delegate.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); + } + + @Override + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) + { + return delegate.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, required); + } + + @Override + public List getServiceAppBindings(UUID serviceInstanceGuid) + { + return delegate.getServiceAppBindings(serviceInstanceGuid); + } + + @Override + public List getAppBindings(UUID applicationGuid) + { + return delegate.getAppBindings(applicationGuid); + } + + @Override + public List getServiceBindingsForApplication(UUID applicationGuid, UUID serviceInstanceGuid) + { + return delegate.getServiceBindingsForApplication(applicationGuid, serviceInstanceGuid); + } + + @Override + public Map getServiceInstanceParameters(UUID guid) + { + return delegate.getServiceInstanceParameters(guid); + } + + @Override + public Map getUserProvidedServiceInstanceParameters(UUID guid) + { + return delegate.getUserProvidedServiceInstanceParameters(guid); + } + + @Override + public Map getServiceBindingParameters(UUID guid) + { + return delegate.getServiceBindingParameters(guid); + } + + @Override + public CloudServiceKey getServiceKey(String serviceInstanceName, String serviceKeyName) + { + return delegate.getServiceKey(serviceInstanceName, serviceKeyName); + } + + @Override + public List getServiceKeys(String serviceInstanceName) + { + return delegate.getServiceKeys(serviceInstanceName); + } + + @Override + public List getServiceKeysWithCredentials(String serviceInstanceName) + { + return delegate.getServiceKeysWithCredentials(serviceInstanceName); + } + + @Override + public List getServiceKeys(CloudServiceInstance serviceInstance) + { + return delegate.getServiceKeys(serviceInstance); + } + + @Override + public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) + { + return delegate.getServiceKeysWithCredentials(serviceInstance); + } + + @Override + public List getServiceOfferings() + { + return delegate.getServiceOfferings(); + } + + @Override + public List getSharedDomains() + { + return delegate.getSharedDomains(); + } + + @Override + public CloudStack getStack(String name) + { + return delegate.getStack(name); + } + + @Override + public CloudStack getStack(String name, boolean required) + { + return delegate.getStack(name, required); + } + + @Override + public List getStacks() + { + return delegate.getStacks(); + } + + @Override + public void rename(String applicationName, String newName) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public void restartApplication(String applicationName) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public void startApplication(String applicationName) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public void stopApplication(String applicationName) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public List unbindServiceInstance(String applicationName, String serviceInstanceName, + ApplicationServicesUpdateCallback applicationServicesUpdateCallback) { + return List.of(); + } + + @Override + public List unbindServiceInstance(String applicationName, String serviceInstanceName) { + return List.of(); + } + + @Override + public List unbindServiceInstance(UUID appGuid, UUID serviceGuid) { + return List.of(); + } + + @Override + public void updateApplicationDiskQuota(String applicationName, int disk) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public void updateApplicationEnv(String applicationName, Map env) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public void updateApplicationInstances(String applicationName, int instances) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public void updateApplicationMemory(String applicationName, int memory) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public void updateApplicationStaging(String applicationName, Staging staging) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public void updateApplicationRoutes(String applicationName, Set routes) + { + record(cs -> cs.appsUpdate, applicationName); + } + + @Override + public String updateServiceBroker(CloudServiceBroker serviceBroker) + { + record(cs -> cs.servicesUpdate, serviceBroker.getName()); + return "dry-run-job-id"; + } + + @Override + public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) + { + record(cs -> cs.servicesUpdate, name); + } + + @Override + public void updateServicePlan(String serviceName, String servicePlan) + { + record(cs -> cs.servicesUpdate, serviceName); + } + + @Override + public void updateServiceParameters(String serviceName, Map parameters) + { + record(cs -> cs.servicesUpdate, serviceName); + } + + @Override + public void updateServiceTags(String serviceName, List tags) + { + record(cs -> cs.servicesUpdate, serviceName); + } + + @Override + public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) + { + record(cs -> cs.servicesUpdate, serviceName); + } + + @Override + public CloudPackage asyncUploadApplicationWithExponentialBackoff(String applicationName, Path file, UploadStatusCallback callback, + Duration overrideTimeout) + { + record(cs -> cs.appsUpdate, applicationName); + return null; + } + + @Override + public Upload getUploadStatus(UUID packageGuid) + { + return delegate.getUploadStatus(packageGuid); + } + + @Override + public CloudTask getTask(UUID taskGuid) + { + return delegate.getTask(taskGuid); + } + + @Override + public List getTasks(String applicationName) + { + return delegate.getTasks(applicationName); + } + + @Override + public CloudTask runTask(String applicationName, CloudTask task) + { + record(cs -> cs.appsCreate, applicationName); + return null; + } + + @Override + public CloudTask cancelTask(UUID taskGuid) + { + return null; + } + + @Override + public CloudBuild createBuild(UUID packageGuid) + { + return null; + } + + @Override + public CloudBuild getBuild(UUID buildGuid) + { + return delegate.getBuild(buildGuid); + } + + @Override + public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) + { + record(cs -> cs.appsCreate, "bindDropletToApp" + " " + dropletGuid); + } + + @Override + public List getBuildsForApplication(UUID applicationGuid) + { + return delegate.getBuildsForApplication(applicationGuid); + } + + @Override + public List getBuildsForPackage(UUID packageGuid) + { + return delegate.getBuildsForPackage(packageGuid); + } + + @Override + public List getApplicationsByMetadataLabelSelector(String labelSelector) + { + return delegate.getApplicationsByMetadataLabelSelector(labelSelector); + } + + @Override + public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) + { + return delegate.getServiceInstancesWithoutAuxiliaryContentByNames(names); + } + + @Override + public List getServiceInstancesByMetadataLabelSelector(String labelSelector) + { + return delegate.getServiceInstancesByMetadataLabelSelector(labelSelector); + } + + @Override + public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) + { + return delegate.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(labelSelector); + } + + @Override + public void updateApplicationMetadata(UUID guid, Metadata metadata) + { + } + + @Override + public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { + } + + @Override + public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { + } + + @Override + public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) + { + return delegate.getCurrentDropletForApplication(applicationGuid); + } + + @Override + public CloudPackage getPackage(UUID packageGuid) + { + return delegate.getPackage(packageGuid); + } + + @Override + public List getPackagesForApplication(UUID applicationGuid) + { + return delegate.getPackagesForApplication(applicationGuid); + } + + @Override + public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) + { + return delegate.getUserRolesBySpaceAndUser(spaceGuid, userGuid); + } + + @Override + public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) + { + record(cs -> cs.appsCreate, "createDockerPackage" + " " + applicationGuid); + return null; + } + + @Override + public CloudAsyncJob getAsyncJob(String jobId) + { + return delegate.getAsyncJob(jobId); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java index 9e51a153ec..87b24a9f89 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java @@ -149,6 +149,11 @@ public static OperationMetadata getMetadata() { .type(ParameterType.BOOLEAN) .defaultValue(false) .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.DRY_RUN.getName()) + .type(ParameterType.BOOLEAN) + .defaultValue(false) + .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.API_REQUEST_PATH.getName()) .type(ParameterType.STRING) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/AsyncFlowableStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/AsyncFlowableStep.java index d99a1141ae..422c1a84eb 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/AsyncFlowableStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/AsyncFlowableStep.java @@ -12,6 +12,9 @@ public abstract class AsyncFlowableStep extends SyncFlowableStep { protected StepPhase executeStep(ProcessContext context) throws Exception { StepPhase stepPhase = context.getVariable(Variables.STEP_PHASE); if (stepPhase == StepPhase.POLL) { + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + return StepPhase.DONE; + } return executeStepExecution(context); } context.setVariable(Variables.ASYNC_STEP_EXECUTION_INDEX, DEFAULT_STEP_EXECUTION_INDEX); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceBindingOperationStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceBindingOperationStep.java index 2972d915ad..0fe51b10b7 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceBindingOperationStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceBindingOperationStep.java @@ -23,6 +23,9 @@ public class CheckServiceBindingOperationStep extends AsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) throws Exception { + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + return StepPhase.DONE; + } List serviceBindings = getServiceBindingsForProcessing(context); if (serviceBindings.isEmpty()) { return StepPhase.DONE; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CollectServicesInProgressStateStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CollectServicesInProgressStateStep.java index 6522d1ef63..71c7f78130 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CollectServicesInProgressStateStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CollectServicesInProgressStateStep.java @@ -30,6 +30,9 @@ public abstract class CollectServicesInProgressStateStep extends TimeoutAsyncFlo @Override protected StepPhase executeAsyncStep(ProcessContext context) { + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + return StepPhase.DONE; + } List existingServicesInProgress = getExistingServicesInProgress(context); if (existingServicesInProgress.isEmpty()) { return StepPhase.DONE; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateAppStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateAppStep.java index 65b757b708..686836b0af 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateAppStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateAppStep.java @@ -66,6 +66,20 @@ public class CreateOrUpdateAppStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) throws FileStorageException { + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + CloudApplicationExtended app = context.getVariable(Variables.APP_TO_PROCESS); + CloudControllerClient client = context.getControllerClient(); + CloudApplication existingApp = client.getApplication(app.getName(), false); + context.setVariable(Variables.EXISTING_APP, existingApp); + var cs = new org.cloudfoundry.multiapps.controller.process.client.DryRunChangeSet(); + if (existingApp == null) { + cs.appsCreate.add(app.getName()); + } else { + cs.appsUpdate.add(app.getName()); + } + context.setVariable(Variables.DRY_RUN_CHANGE_SET, cs); + return StepPhase.DONE; + } CloudApplicationExtended app = context.getVariable(Variables.APP_TO_PROCESS); CloudControllerClient client = context.getControllerClient(); CloudApplication existingApp = client.getApplication(app.getName(), false); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineApplicationServiceBindingActionsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineApplicationServiceBindingActionsStep.java index 2ff167cf0d..f9355d0dcb 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineApplicationServiceBindingActionsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineApplicationServiceBindingActionsStep.java @@ -37,6 +37,11 @@ public class DetermineApplicationServiceBindingActionsStep extends SyncFlowableS @Override protected StepPhase executeStep(ProcessContext context) { + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + context.setVariable(Variables.SHOULD_UNBIND_SERVICE_FROM_APP, false); + context.setVariable(Variables.SHOULD_BIND_SERVICE_TO_APP, false); + return StepPhase.DONE; + } CloudControllerClient client = context.getControllerClient(); CloudServiceBinding serviceBindingToDelete = context.getVariable(Variables.SERVICE_BINDING_TO_DELETE); CloudApplicationExtended appToProcess = context.getVariable(Variables.APP_TO_PROCESS); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineDesiredStateAchievingActionsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineDesiredStateAchievingActionsStep.java index 490af1aa54..e0f5b014fa 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineDesiredStateAchievingActionsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineDesiredStateAchievingActionsStep.java @@ -33,6 +33,10 @@ public class DetermineDesiredStateAchievingActionsStep extends SyncFlowableStep @Override protected StepPhase executeStep(ProcessContext context) { + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + context.setVariable(Variables.APP_STATE_ACTIONS_TO_EXECUTE, new ArrayList<>()); + return StepPhase.DONE; + } String appName = context.getVariable(Variables.APP_TO_PROCESS) .getName(); CloudControllerClient client = context.getControllerClient(); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStep.java index db13a20b3c..73ec6cfd0b 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStep.java @@ -64,6 +64,13 @@ private StepPhase calculateDeleteActions(ProcessContext context, String serviceI } List serviceBindings = controllerClient.getServiceAppBindings(serviceInstance.getGuid()); List serviceKeys = controllerClient.getServiceKeys(serviceInstance); + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + var cs = new org.cloudfoundry.multiapps.controller.process.client.DryRunChangeSet(); + cs.servicesDelete.add(serviceInstanceToDelete); + context.setVariable(Variables.DRY_RUN_CHANGE_SET, cs); + context.setVariable(Variables.SERVICE_DELETION_ACTIONS, Collections.emptyList()); + return StepPhase.DONE; + } if (isDeletePossible(context, serviceBindings, serviceKeys)) { context.getStepLogger() .debug(Messages.WILL_DELETE_SERVICE_BINDINGS_SERVICE_KEYS_AND_SERVICE_INSTANCE_0, serviceInstanceToDelete); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessContext.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessContext.java index 8fa6cc189d..f43ad6df9c 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessContext.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessContext.java @@ -8,6 +8,7 @@ import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.client.DryRunCloudControllerClient; import org.cloudfoundry.multiapps.controller.process.client.LoggingCloudControllerClient; import org.cloudfoundry.multiapps.controller.process.util.StepLogger; import org.cloudfoundry.multiapps.controller.process.variables.Variable; @@ -42,7 +43,7 @@ public CloudControllerClient getControllerClient() { CloudControllerClient delegate = clientProvider.getControllerClient(userGuid, spaceGuid, correlationId); Set secretParameters = VariableHandling.get(execution, Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); - return new LoggingCloudControllerClient(delegate, stepLogger, dynamicSecureSerialization); + return wrapForDryRunIfEnabled(new LoggingCloudControllerClient(delegate, stepLogger, dynamicSecureSerialization)); } public CloudControllerClient getControllerClient(String spaceGuid) { @@ -51,7 +52,14 @@ public CloudControllerClient getControllerClient(String spaceGuid) { CloudControllerClient delegate = clientProvider.getControllerClient(userGuid, spaceGuid, correlationId); Set secretParameters = VariableHandling.get(execution, Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); - return new LoggingCloudControllerClient(delegate, stepLogger, dynamicSecureSerialization); + return wrapForDryRunIfEnabled(new LoggingCloudControllerClient(delegate, stepLogger, dynamicSecureSerialization)); + } + + private CloudControllerClient wrapForDryRunIfEnabled(CloudControllerClient client) { + if (Boolean.TRUE.equals(getVariable(Variables.DRY_RUN))) { + return new DryRunCloudControllerClient(client, this); + } + return client; } public T getRequiredVariable(Variable variable) { diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PublishConfigurationEntriesStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PublishConfigurationEntriesStep.java index 0d39f0a0a7..819f3271c5 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PublishConfigurationEntriesStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PublishConfigurationEntriesStep.java @@ -2,7 +2,9 @@ import java.text.MessageFormat; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -47,6 +49,7 @@ protected StepPhase executeStep(ProcessContext context) { if (CollectionUtils.isEmpty(entriesToPublish)) { context.setVariable(Variables.PUBLISHED_ENTRIES, Collections.emptyList()); getStepLogger().debug(Messages.NO_PUBLIC_PROVIDED_DEPENDENCIES_FOR_PUBLISHING); + collectOutputs(context, app); return StepPhase.DONE; } Set dynamicResolvableParameters = context.getVariable(Variables.DYNAMIC_RESOLVABLE_PARAMETERS); @@ -61,6 +64,7 @@ protected StepPhase executeStep(ProcessContext context) { context.setVariable(Variables.PUBLISHED_ENTRIES, publishedEntries); getStepLogger().debug(Messages.PUBLIC_PROVIDED_DEPENDENCIES_PUBLISHED); + collectOutputs(context, app); return StepPhase.DONE; } @@ -108,4 +112,18 @@ private ConfigurationEntry getExistingEntry(ConfigurationEntry targetEntry) { return existingEntries.isEmpty() ? null : existingEntries.get(0); } + private void collectOutputs(ProcessContext context, CloudApplicationExtended app) { + app.getRoutes() + .stream() + .findFirst() + .ifPresent(route -> { + Map outputs = context.getVariable(Variables.DEPLOYMENT_OUTPUTS); + if (outputs == null) { + outputs = new LinkedHashMap<>(); + } + outputs.put(app.getName() + ".url", "https://" + route.getUrl()); + context.setVariable(Variables.DEPLOYMENT_OUTPUTS, outputs); + }); + } + } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ScaleAppStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ScaleAppStep.java index a8604e5fb0..e513ddc46a 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ScaleAppStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ScaleAppStep.java @@ -17,6 +17,9 @@ public class ScaleAppStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + return StepPhase.DONE; + } CloudApplicationExtended app = context.getVariable(Variables.APP_TO_PROCESS); CloudApplication existingApp = context.getVariable(Variables.EXISTING_APP); CloudControllerClient client = context.getControllerClient(); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ServiceStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ServiceStep.java index cfb562278e..08c8f072b3 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ServiceStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ServiceStep.java @@ -29,6 +29,9 @@ public abstract class ServiceStep extends AsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) { CloudServiceInstanceExtended serviceToProcess = context.getVariable(Variables.SERVICE_TO_PROCESS); + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + return StepPhase.DONE; + } OperationExecutionState executionState = executeOperation(context, context.getControllerClient(), serviceToProcess); if (executionState == OperationExecutionState.FINISHED) { return StepPhase.DONE; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStep.java index 3874382dbb..6fab81e19c 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStep.java @@ -59,6 +59,9 @@ public class UploadAppStep extends TimeoutAsyncFlowableStep { @Override public StepPhase executeAsyncStep(ProcessContext context) throws FileStorageException { + if (Boolean.TRUE.equals(context.getVariable(Variables.DRY_RUN))) { + return StepPhase.DONE; + } CloudApplicationExtended applicationToProcess = context.getVariable(Variables.APP_TO_PROCESS); getStepLogger().info(Messages.UPLOADING_APP, applicationToProcess.getName()); 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 efe9f0f026..05b8e3ae41 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 @@ -28,7 +28,9 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; import org.cloudfoundry.multiapps.controller.core.model.DynamicResolvableParameter; +import org.cloudfoundry.multiapps.controller.api.model.FailureReason; import org.cloudfoundry.multiapps.controller.core.model.ErrorType; +import org.cloudfoundry.multiapps.controller.process.client.DryRunChangeSet; import org.cloudfoundry.multiapps.controller.core.model.IncrementalAppInstanceUpdateConfiguration; import org.cloudfoundry.multiapps.controller.core.model.Phase; import org.cloudfoundry.multiapps.controller.core.model.SubprocessPhase; @@ -520,6 +522,10 @@ public interface Variables { .name("errorType") .type(ErrorType.class) .build(); + Variable FAILURE_REASON_CODE = ImmutableEnumVariable. builder() + .name("failureReasonCode") + .type(FailureReason.Code.class) + .build(); Variable IDLE_MTA_COLOR = ImmutableEnumVariable. builder() .name("idleMtaColor") .type(ApplicationColor.class) @@ -987,4 +993,19 @@ public Serializer> getSerializer() { .name("parentProcessInstanceId") .defaultValue(null) .build(); + + Variable DRY_RUN = ImmutableSimpleVariable. builder() + .name("dryRun") + .defaultValue(false) + .build(); + + Variable DRY_RUN_CHANGE_SET = ImmutableJsonBinaryVariable. builder() + .name("dryRunChangeSet") + .type(Variable.typeReference(DryRunChangeSet.class)) + .build(); + + Variable> DEPLOYMENT_OUTPUTS = ImmutableJsonBinaryVariable.> builder() + .name("deploymentOutputs") + .type(new TypeReference>() {}) + .build(); } 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 74aa96ee69..74eecf2914 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 @@ -91,6 +91,7 @@ + @@ -114,6 +115,7 @@ + @@ -149,6 +151,7 @@ + @@ -187,6 +190,7 @@ + @@ -223,6 +227,7 @@ + @@ -354,6 +359,7 @@ + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn index 898fb6a75d..843abb8cd5 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn @@ -39,6 +39,7 @@ + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn index a2610d963b..6f0ff38286 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn @@ -74,11 +74,17 @@ + + + + + + @@ -117,6 +123,7 @@ + @@ -148,6 +155,7 @@ + @@ -196,6 +204,7 @@ + @@ -253,6 +262,7 @@ + diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java index 1e35d8dfff..519017582d 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java @@ -52,6 +52,7 @@ protected String[] getParametersIds() { Variables.IS_SECURITY_ENABLED.getName(), Variables.DISPOSABLE_USER_PROVIDED_SERVICE_NAME.getName(), Variables.IS_DISPOSABLE_USER_PROVIDED_SERVICE_ENABLED.getName(), + Variables.DRY_RUN.getName(), Variables.API_REQUEST_PATH.getName() // @formatter:on }; diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-01.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-01.json index 9a26007ea4..8a0950d5c3 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-01.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-01.json @@ -9,6 +9,7 @@ "moduleName": "node-hello-world-db", "boundMtaServices": [], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" }, { @@ -16,6 +17,7 @@ "moduleName": "node-hello-world-backend", "boundMtaServices": [], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" }, { @@ -23,6 +25,7 @@ "moduleName": "node-hello-world", "boundMtaServices": [], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" } ], diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-02.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-02.json index 1c9eb83a86..8fa9da4319 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-02.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-02.json @@ -12,6 +12,7 @@ "node-hdi-container" ], "providedDependencyNames": [], + "requiredDependencyNames": [], "routes": [] } ], diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-03.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-03.json index 71bec6aa01..8a2f6762ce 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-03.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-03.json @@ -8,31 +8,36 @@ "moduleName": "a", "name": "a", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "b", "name": "b", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "c", "name": "c", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "d", "name": "d", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "e", "name": "e", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [] diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-04.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-04.json index 26c6b719bc..251351a56e 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-04.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-04.json @@ -8,13 +8,15 @@ "moduleName": "d", "name": "d", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "e", "name": "e", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [] diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-05.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-05.json index 83d4a7661c..1b3b40f6d2 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-05.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-05.json @@ -10,7 +10,8 @@ "boundMtaServices": [ "s-1" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "b", @@ -19,13 +20,15 @@ "s-1", "s-2" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "c", "name": "c", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "d", @@ -34,13 +37,15 @@ "s-3", "s-4" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "e", "name": "e", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [ diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-06.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-06.json index 31d1d5d6ba..79d00cc128 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-06.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-06.json @@ -11,7 +11,8 @@ "s-1", "s-2" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "e", @@ -19,7 +20,8 @@ "boundMtaServices": [ "s-1" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [ diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-07.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-07.json index b268c0b3b2..2e8b95abcd 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-07.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-07.json @@ -8,19 +8,22 @@ "moduleName": "a", "name": "namespace.a", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "b", "name": "namespace.b", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "c", "name": "namespace.c", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [] diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-08.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-08.json index 6563cd994d..64da515f39 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-08.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-08.json @@ -11,13 +11,15 @@ "s-1", "s-2" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "b", "name": "b", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "c", @@ -25,7 +27,8 @@ "boundMtaServices": [ "s-3" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "d", @@ -34,13 +37,15 @@ "s-1", "s-3" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "e", "name": "e", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [ diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-09.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-09.json index dc936db0fb..85ab79636d 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-09.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-09.json @@ -8,13 +8,15 @@ "moduleName": "a", "name": "namespace.a", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "b", "name": "namespace.b", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [] diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-10.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-10.json index b92772c11f..f1ee16c831 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-10.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-10.json @@ -33,13 +33,15 @@ "moduleName": "d", "name": "d", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "e", "name": "e", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [] diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-12.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-12.json index 53a26928d5..8015f1f316 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-12.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-12.json @@ -10,7 +10,8 @@ "boundMtaService": [ "node-hdi-container" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "node-hello-world-backend", @@ -19,7 +20,8 @@ "uaa", "node-hdi-container" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "node-hello-world", @@ -27,7 +29,8 @@ "boundMtaService": [ "uaa" ], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [ diff --git a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-13.json b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-13.json index 7b14507140..2364ed2069 100644 --- a/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-13.json +++ b/multiapps-controller-process/src/test/resources/org/cloudfoundry/multiapps/controller/process/steps/deployed-mta-13.json @@ -8,19 +8,22 @@ "moduleName": "a", "name": "a", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "b", "name": "b", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] }, { "moduleName": "c", "name": "c", "boundMtaServices": [], - "providedDependencyNames": [] + "providedDependencyNames": [], + "requiredDependencyNames": [] } ], "services": [] From 7c10e43cc4210bcfb0a9f2ab327eeca5780714f9 Mon Sep 17 00:00:00 2001 From: vkalapov <87693906+vkalapov@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:39:05 +0200 Subject: [PATCH 2/2] Add DryRunResult model and Operation API fields for dry-run Adds the DryRunResult schema and wires dryRunResult/outputs onto the Operation model and mtarest.yaml, consumed by the dry-run deploy flow. --- .../controller/api/model/DryRunResult.java | 40 +++++++++++++++ .../controller/api/model/Operation.java | 6 +++ .../src/main/resources/mtarest.yaml | 51 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/DryRunResult.java diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/DryRunResult.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/DryRunResult.java new file mode 100644 index 0000000000..c1b55a9441 --- /dev/null +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/DryRunResult.java @@ -0,0 +1,40 @@ +package org.cloudfoundry.multiapps.controller.api.model; + +import java.util.List; + +import org.immutables.value.Value; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@Value.Immutable +@JsonSerialize(as = ImmutableDryRunResult.class) +@JsonDeserialize(as = ImmutableDryRunResult.class) +public interface DryRunResult { + + @JsonProperty("appsToCreate") + List getAppsToCreate(); + + @JsonProperty("appsToUpdate") + List getAppsToUpdate(); + + @JsonProperty("appsToDelete") + List getAppsToDelete(); + + @JsonProperty("servicesToCreate") + List getServicesToCreate(); + + @JsonProperty("servicesToUpdate") + List getServicesToUpdate(); + + @JsonProperty("servicesToDelete") + List getServicesToDelete(); + + @JsonProperty("routesToCreate") + List getRoutesToCreate(); + + @JsonProperty("routesToDelete") + List getRoutesToDelete(); + +} diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/Operation.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/Operation.java index 9aa5af8ac6..c64d64cda1 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/Operation.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/Operation.java @@ -100,6 +100,12 @@ public static List getFinalStates() { @Nullable public abstract ErrorType getErrorType(); + @Nullable + public abstract DryRunResult getDryRunResult(); + + @Nullable + public abstract Map getOutputs(); + public abstract List getMessages(); @AllowNulls diff --git a/multiapps-controller-api/src/main/resources/mtarest.yaml b/multiapps-controller-api/src/main/resources/mtarest.yaml index 851d4d6640..0a2c934424 100644 --- a/multiapps-controller-api/src/main/resources/mtarest.yaml +++ b/multiapps-controller-api/src/main/resources/mtarest.yaml @@ -779,6 +779,14 @@ definitions: enum: - "CONTENT" - "INFRASTRUCTURE" + dryRunResult: + readOnly: true + $ref: "#/definitions/DryRunResult" + outputs: + type: "object" + readOnly: true + additionalProperties: + type: "string" messages: type: "array" readOnly: true @@ -789,6 +797,49 @@ definitions: readOnly: true additionalProperties: type: "object" + DryRunResult: + type: "object" + properties: + appsToCreate: + type: "array" + readOnly: true + items: + type: "string" + appsToUpdate: + type: "array" + readOnly: true + items: + type: "string" + appsToDelete: + type: "array" + readOnly: true + items: + type: "string" + servicesToCreate: + type: "array" + readOnly: true + items: + type: "string" + servicesToUpdate: + type: "array" + readOnly: true + items: + type: "string" + servicesToDelete: + type: "array" + readOnly: true + items: + type: "string" + routesToCreate: + type: "array" + readOnly: true + items: + type: "string" + routesToDelete: + type: "array" + readOnly: true + items: + type: "string" ProcessType: type: "object" properties: