org.cloudfoundry.multiapps.controller.process.steps.UpdateSubscribersStep Maven / Gradle / Ivy
package org.cloudfoundry.multiapps.controller.process.steps;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.apache.commons.collections4.ListUtils;
import org.cloudfoundry.multiapps.common.SLException;
import org.cloudfoundry.multiapps.common.util.JsonUtil;
import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended;
import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientFactory;
import org.cloudfoundry.multiapps.controller.core.cf.CloudHandlerFactory;
import org.cloudfoundry.multiapps.controller.core.cf.v2.ApplicationCloudModelBuilder;
import org.cloudfoundry.multiapps.controller.core.helpers.ApplicationAttributes;
import org.cloudfoundry.multiapps.controller.core.helpers.ClientHelper;
import org.cloudfoundry.multiapps.controller.core.helpers.DummyConfigurationFilterParser;
import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper;
import org.cloudfoundry.multiapps.controller.core.helpers.ReferencingPropertiesVisitor;
import org.cloudfoundry.multiapps.controller.core.helpers.v2.ConfigurationReferencesResolver;
import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters;
import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization;
import org.cloudfoundry.multiapps.controller.core.security.token.TokenService;
import org.cloudfoundry.multiapps.controller.persistence.model.CloudTarget;
import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationEntry;
import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationSubscription;
import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationSubscription.ModuleDto;
import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationSubscription.RequiredDependencyDto;
import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationEntryService;
import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationSubscriptionService;
import org.cloudfoundry.multiapps.controller.process.Messages;
import org.cloudfoundry.multiapps.controller.process.flowable.FlowableFacade;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.helpers.VisitableObject;
import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor;
import org.cloudfoundry.multiapps.mta.model.Module;
import org.cloudfoundry.multiapps.mta.parsers.v2.DeploymentDescriptorParser;
import org.cloudfoundry.multiapps.mta.parsers.v2.ModuleParser;
import org.cloudfoundry.multiapps.mta.resolvers.Reference;
import org.cloudfoundry.multiapps.mta.resolvers.ReferencePattern;
import org.cloudfoundry.multiapps.mta.resolvers.ResolverBuilder;
import org.cloudfoundry.multiapps.mta.util.NameUtil;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import com.sap.cloudfoundry.client.facade.CloudControllerClient;
import com.sap.cloudfoundry.client.facade.CloudOperationException;
import com.sap.cloudfoundry.client.facade.domain.CloudApplication;
import com.sap.cloudfoundry.client.facade.domain.CloudSpace;
import com.sap.cloudfoundry.client.facade.rest.CloudSpaceClient;
@Named("updateSubscribersStep")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class UpdateSubscribersStep extends SyncFlowableStep {
/*
* This schema version will be used only for the handling of the subscription entities and it should always be the same as the latest
* version that is supported by the deploy service, as it is assumed that the latest version of the MTA specification will always
* support a superset of the features supported by the previous versions.
*
* The major schema version of the MTA that is currently being deployed should NOT be used instead of this one, as problems could occur
* if the subscriber has a different major schema version. If, for example, the current MTA has a major schema version 1, and the
* subscriber has a major schema version 2, then this would result in the creation of a handler factory for version 1. That would cause
* the update of the subscriber to fail, as required dependency entities do not exist in version 1 of the MTA specification and
* therefore cannot be parsed by the version 1 parser that would be returned by that handler factory.
*/
// FIXME: Either store the major schema version in the subscriptions table
// or change this to "3" and verify that everything is
// working...
private static final int MAJOR_SCHEMA_VERSION = 2;
private static final String SCHEMA_VERSION = "2.1.0";
private static final String DUMMY_VERSION = "1.0.0";
protected BiFunction targetCalculator = ClientHelper::attemptToFindSpace;
@Inject
private ConfigurationSubscriptionService configurationSubscriptionService;
@Inject
private ConfigurationEntryService configurationEntryService;
@Inject
private FlowableFacade flowableFacade;
@Inject
private ModuleToDeployHelper moduleToDeployHelper;
@Inject
private CloudControllerClientFactory clientFactory;
@Inject
private TokenService tokenService;
@Override
protected StepPhase executeStep(ProcessContext context) {
getStepLogger().debug(Messages.UPDATING_SUBSCRIBERS);
List publishedEntries = StepsUtil.getPublishedEntriesFromSubProcesses(context, flowableFacade);
List deletedEntries = StepsUtil.getDeletedEntriesFromAllProcesses(context, flowableFacade);
List updatedEntries = ListUtils.union(publishedEntries, deletedEntries);
List updatedSubscribers = new ArrayList<>();
List updatedServiceBrokerSubscribers = new ArrayList<>();
List subscriptions = configurationSubscriptionService.createQuery()
.onSelectMatching(updatedEntries)
.list();
ClientHelper clientHelper = new ClientHelper(createSpaceClient(context));
for (ConfigurationSubscription subscription : subscriptions) {
CloudSpace target = targetCalculator.apply(clientHelper, subscription.getSpaceId());
if (target == null) {
getStepLogger().warn(Messages.COULD_NOT_COMPUTE_ORG_AND_SPACE, subscription.getSpaceId());
continue;
}
CloudControllerClient client = getClient(context, target);
CloudApplication subscriberApp = client.getApplication(subscription.getAppName());
Map appEnv = client.getApplicationEnvironment(subscriberApp.getGuid());
CloudApplication updatedApplication = updateSubscriber(context, subscription, client, subscriberApp, appEnv);
if (updatedApplication != null) {
addApplicationToProperList(updatedSubscribers, updatedServiceBrokerSubscribers, updatedApplication, appEnv);
}
}
context.setVariable(Variables.UPDATED_SUBSCRIBERS, removeDuplicates(updatedSubscribers));
context.setVariable(Variables.UPDATED_SERVICE_BROKER_SUBSCRIBERS, updatedServiceBrokerSubscribers);
getStepLogger().debug(Messages.SUBSCRIBERS_UPDATED);
return StepPhase.DONE;
}
@Override
protected String getStepErrorMessage(ProcessContext context) {
return Messages.ERROR_UPDATING_SUBSCRIBERS;
}
private CloudSpaceClient createSpaceClient(ProcessContext context) {
var user = context.getVariable(Variables.USER);
var token = tokenService.getToken(user);
return clientFactory.createSpaceClient(token);
}
private void addApplicationToProperList(List updatedSubscribers,
List updatedServiceBrokerSubscribers, CloudApplication updatedApplication,
Map appEnv) {
ApplicationAttributes appAttributes = ApplicationAttributes.fromApplication(updatedApplication, appEnv);
if (appAttributes.get(SupportedParameters.CREATE_SERVICE_BROKER, Boolean.class, false)) {
updatedServiceBrokerSubscribers.add(updatedApplication);
} else {
updatedSubscribers.add(updatedApplication);
}
}
private List removeDuplicates(List applications) {
Map applicationsMap = new LinkedHashMap<>();
for (CloudApplication application : applications) {
applicationsMap.put(application.getGuid(), application);
}
return new ArrayList<>(applicationsMap.values());
}
private CloudApplication updateSubscriber(ProcessContext context, ConfigurationSubscription subscription, CloudControllerClient client,
CloudApplication subscriberApp, Map appEnv) {
try {
return attemptToUpdateSubscriber(context, client, subscription, subscriberApp, appEnv);
} catch (CloudOperationException | SLException e) {
String appName = subscription.getAppName();
String mtaId = subscription.getMtaId();
String subscriptionName = getRequiredDependency(subscription).getName();
getStepLogger().warn(e, Messages.COULD_NOT_UPDATE_SUBSCRIBER, appName, mtaId, subscriptionName);
return null;
}
}
private CloudApplication attemptToUpdateSubscriber(ProcessContext context, CloudControllerClient client,
ConfigurationSubscription subscription, CloudApplication subscriberApp,
Map appEnv) {
CloudHandlerFactory handlerFactory = CloudHandlerFactory.forSchemaVersion(MAJOR_SCHEMA_VERSION);
DeploymentDescriptor dummyDescriptor = buildDummyDescriptor(subscription, handlerFactory);
getStepLogger().debug(org.cloudfoundry.multiapps.controller.core.Messages.DEPLOYMENT_DESCRIPTOR,
SecureSerialization.toJson(dummyDescriptor));
ConfigurationReferencesResolver resolver = handlerFactory.getConfigurationReferencesResolver(configurationEntryService,
new DummyConfigurationFilterParser(subscription.getFilter()),
new CloudTarget(context.getVariable(Variables.ORGANIZATION_NAME),
context.getVariable(Variables.SPACE_NAME)),
configuration);
resolver.resolve(dummyDescriptor);
getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, SecureSerialization.toJson(dummyDescriptor));
dummyDescriptor = handlerFactory.getDescriptorReferenceResolver(dummyDescriptor, new ResolverBuilder(), new ResolverBuilder(),
new ResolverBuilder(),
SupportedParameters.DYNAMIC_RESOLVABLE_PARAMETERS)
.resolve();
getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, SecureSerialization.toJson(dummyDescriptor));
ApplicationCloudModelBuilder applicationCloudModelBuilder = handlerFactory.getApplicationCloudModelBuilder(dummyDescriptor,
shouldUsePrettyPrinting(),
null, "",
context.getVariable(Variables.MTA_NAMESPACE),
getStepLogger(),
StepsUtil.getAppSuffixDeterminer(context),
client, false);
Module module = dummyDescriptor.getModules()
.get(0);
CloudApplicationExtended application = applicationCloudModelBuilder.build(module, moduleToDeployHelper);
Map updatedEnvironment = application.getEnv();
Map currentEnvironment = new LinkedHashMap<>(appEnv);
boolean neededToBeUpdated = updateCurrentEnvironment(currentEnvironment, updatedEnvironment,
getPropertiesToTransfer(subscription, resolver));
if (!neededToBeUpdated) {
return null;
}
getStepLogger().info(Messages.UPDATING_SUBSCRIBER, subscription.getAppName(), subscription.getMtaId(),
getRequiredDependency(subscription).getName());
client.updateApplicationEnv(subscriberApp.getName(), currentEnvironment);
return subscriberApp;
}
private boolean updateCurrentEnvironment(Map currentEnvironment, Map updatedEnvironment,
List propertiesToTransfer) {
boolean neededToBeUpdated = false;
for (String propertyToTransfer : propertiesToTransfer) {
String currentProperty = currentEnvironment.get(propertyToTransfer);
String updatedProperty = updatedEnvironment.get(propertyToTransfer);
if (!Objects.equals(currentProperty, updatedProperty)) {
neededToBeUpdated = true;
currentEnvironment.put(propertyToTransfer, updatedEnvironment.get(propertyToTransfer));
}
}
return neededToBeUpdated;
}
private List getPropertiesToTransfer(ConfigurationSubscription subscription, ConfigurationReferencesResolver resolver) {
List result = new ArrayList<>(getFirstComponents(resolver.getExpandedProperties()));
String listName = getRequiredDependency(subscription).getList();
if (listName == null) {
result.addAll(getPropertiesWithReferencesToConfigurationResource(subscription));
result.addAll(getRequiredDependency(subscription).getProperties()
.keySet());
} else {
result.add(listName);
}
return result;
}
private List getPropertiesWithReferencesToConfigurationResource(ConfigurationSubscription subscription) {
ReferenceDetector detector = new ReferenceDetector(getRequiredDependency(subscription).getName());
new VisitableObject(subscription.getModuleDto()
.getProperties()).accept(detector);
return getFirstComponents(detector.getRelevantProperties());
}
private RequiredDependencyDto getRequiredDependency(ConfigurationSubscription subscription) {
return subscription.getModuleDto()
.getRequiredDependencies()
.get(0);
}
private List getFirstComponents(List properties) {
return properties.stream()
.map(this::getFirstComponent)
.collect(Collectors.toList());
}
private String getFirstComponent(String propertyName) {
int index = propertyName.indexOf(NameUtil.DEFAULT_PREFIX_SEPARATOR);
if (index != -1) {
return propertyName.substring(0, index);
}
return propertyName;
}
private CloudControllerClient getClient(ProcessContext context, CloudSpace space) {
return context.getControllerClient(space.getGuid()
.toString());
}
private DeploymentDescriptor buildDummyDescriptor(ConfigurationSubscription subscription, CloudHandlerFactory handlerFactory) {
ModuleDto moduleDto = subscription.getModuleDto();
String resourceJson = JsonUtil.toJson(subscription.getResourceDto());
Map resourceMap = JsonUtil.convertJsonToMap(resourceJson);
Map moduleMap = new TreeMap<>();
moduleMap.put(ModuleParser.NAME, moduleDto.getName());
moduleMap.put(ModuleParser.TYPE, moduleDto.getName());
moduleMap.put(ModuleParser.PROPERTIES, moduleDto.getProperties());
moduleMap.put(ModuleParser.PROVIDES, JsonUtil.convertJsonToList(JsonUtil.toJson(moduleDto.getProvidedDependencies())));
moduleMap.put(ModuleParser.REQUIRES, JsonUtil.convertJsonToList(JsonUtil.toJson(moduleDto.getRequiredDependencies())));
Map dummyDescriptorMap = new TreeMap<>();
dummyDescriptorMap.put(DeploymentDescriptorParser.SCHEMA_VERSION, SCHEMA_VERSION);
dummyDescriptorMap.put(DeploymentDescriptorParser.ID, subscription.getMtaId());
dummyDescriptorMap.put(DeploymentDescriptorParser.MODULES, Collections.singletonList(moduleMap));
dummyDescriptorMap.put(DeploymentDescriptorParser.VERSION, DUMMY_VERSION);
dummyDescriptorMap.put(DeploymentDescriptorParser.RESOURCES, Collections.singletonList(resourceMap));
return handlerFactory.getDescriptorParser()
.parseDeploymentDescriptor(dummyDescriptorMap);
}
protected boolean shouldUsePrettyPrinting() {
return true;
}
private static class ReferenceDetector extends ReferencingPropertiesVisitor {
public ReferenceDetector(String name) {
super(ReferencePattern.FULLY_QUALIFIED, reference -> name.equals(reference.getDependencyName()));
}
private final List relevantProperties = new ArrayList<>();
@Override
protected Object visit(String key, String value, List references) {
relevantProperties.add(key);
return value;
}
public List getRelevantProperties() {
return relevantProperties;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy