All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cloudfoundry.multiapps.controller.process.steps.CreateOrUpdateAppStep Maven / Gradle / Ivy

There is a newer version: 1.183.0
Show newest version
package org.cloudfoundry.multiapps.controller.process.steps;

import java.text.MessageFormat;
import java.util.HashMap;
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.BooleanSupplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.cloudfoundry.multiapps.common.util.JsonUtil;
import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended;
import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended;
import org.cloudfoundry.multiapps.controller.client.lib.domain.ServiceKeyToInject;
import org.cloudfoundry.multiapps.controller.core.cf.clients.AppBoundServiceInstanceNamesGetter;
import org.cloudfoundry.multiapps.controller.core.cf.clients.WebClientFactory;
import org.cloudfoundry.multiapps.controller.core.helpers.ApplicationFileDigestDetector;
import org.cloudfoundry.multiapps.controller.core.security.token.TokenService;
import org.cloudfoundry.multiapps.controller.persistence.services.FileStorageException;
import org.cloudfoundry.multiapps.controller.process.Messages;
import org.cloudfoundry.multiapps.controller.process.util.ApplicationAttributeUpdater;
import org.cloudfoundry.multiapps.controller.process.util.ApplicationAttributeUpdater.UpdateState;
import org.cloudfoundry.multiapps.controller.process.util.ControllerClientFacade;
import org.cloudfoundry.multiapps.controller.process.util.DiskQuotaApplicationAttributeUpdater;
import org.cloudfoundry.multiapps.controller.process.util.ElementUpdater.UpdateStrategy;
import org.cloudfoundry.multiapps.controller.process.util.EnvironmentApplicationAttributeUpdater;
import org.cloudfoundry.multiapps.controller.process.util.MemoryApplicationAttributeUpdater;
import org.cloudfoundry.multiapps.controller.process.util.StagingApplicationAttributeUpdater;
import org.cloudfoundry.multiapps.controller.process.util.UrisApplicationAttributeUpdater;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
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.CloudCredentials;
import com.sap.cloudfoundry.client.facade.domain.CloudApplication;
import com.sap.cloudfoundry.client.facade.dto.ApplicationToCreateDto;
import com.sap.cloudfoundry.client.facade.dto.ImmutableApplicationToCreateDto;

import static org.cloudfoundry.multiapps.controller.process.steps.StepsUtil.disableAutoscaling;

@Named("createOrUpdateAppStep")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class CreateOrUpdateAppStep extends SyncFlowableStep {

    protected BooleanSupplier shouldPrettyPrint = () -> true;

    @Inject
    private TokenService tokenService;
    @Inject
    private WebClientFactory webClientFactory;

    @Override
    protected StepPhase executeStep(ProcessContext context) throws FileStorageException {
        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);

        StepFlowHandler flowHandler = createStepFlowHandler(context, client, app, existingApp);

        flowHandler.printStepStartMessage();

        flowHandler.injectServiceKeysCredentialsInAppEnv();
        flowHandler.handleApplicationAttributes();
        flowHandler.handleApplicationServices();
        flowHandler.printStepEndMessage();

        return StepPhase.DONE;
    }

    @Override
    protected String getStepErrorMessage(ProcessContext context) {
        return MessageFormat.format(Messages.ERROR_CREATING_OR_UPDATING_APP, context.getVariable(Variables.APP_TO_PROCESS)
                                                                                    .getName());
    }

    protected AppBoundServiceInstanceNamesGetter getAppBoundServiceInstanceNamesGetter(ProcessContext context) {
        String user = context.getVariable(Variables.USER);
        String correlationId = context.getVariable(Variables.CORRELATION_ID);
        var token = tokenService.getToken(user);
        var credentials = new CloudCredentials(token, true);
        return new AppBoundServiceInstanceNamesGetter(configuration, webClientFactory, credentials, correlationId);
    }

    private StepFlowHandler createStepFlowHandler(ProcessContext context, CloudControllerClient client, CloudApplicationExtended app,
                                                  CloudApplication existingApp) {
        if (existingApp == null) {
            return new CreateAppFlowHandler(context, client, app);
        }
        return new UpdateAppFlowHandler(context, client, app, existingApp);
    }

    private abstract class StepFlowHandler {

        final ProcessContext context;
        CloudApplicationExtended app;
        final CloudControllerClient client;

        public StepFlowHandler(ProcessContext context, CloudControllerClient client, CloudApplicationExtended app) {
            this.context = context;
            this.app = app;
            this.client = client;
        }

        public void injectServiceKeysCredentialsInAppEnv() {
            Map appEnv = new LinkedHashMap<>(app.getEnv());
            Map appServiceKeysCredentials = buildServiceKeysCredentials(client, app);
            appEnv.putAll(appServiceKeysCredentials);
            app = ImmutableCloudApplicationExtended.copyOf(app)
                                                   .withEnv(appEnv);
            context.setVariable(Variables.APP_TO_PROCESS, app);
        }

        private Map buildServiceKeysCredentials(CloudControllerClient client, CloudApplicationExtended app) {
            Map serviceKeys = new HashMap<>();
            for (ServiceKeyToInject serviceKeyToInject : app.getServiceKeysToInject()) {
                var serviceKey = client.getServiceKey(serviceKeyToInject.getServiceName(), serviceKeyToInject.getServiceKeyName());
                String serviceKeyCredentials = JsonUtil.toJson(serviceKey.getCredentials(), shouldPrettyPrint.getAsBoolean());
                serviceKeys.put(serviceKeyToInject.getEnvVarName(), serviceKeyCredentials);
            }
            return serviceKeys;
        }

        public abstract void printStepStartMessage();

        public abstract void handleApplicationAttributes();

        public abstract void handleApplicationServices();

        public abstract void printStepEndMessage();
    }

    private class CreateAppFlowHandler extends StepFlowHandler {

        public CreateAppFlowHandler(ProcessContext context, CloudControllerClient client, CloudApplicationExtended app) {
            super(context, client, app);
        }

        @Override
        public void handleApplicationAttributes() {
            Integer diskQuotaInMb = (app.getDiskQuota() != 0) ? app.getDiskQuota() : null;
            Integer memoryInMb = (app.getMemory() != 0) ? app.getMemory() : null;
            if (app.getDockerInfo() != null) {
                context.getStepLogger()
                       .info(Messages.CREATING_APP_FROM_DOCKER_IMAGE, app.getName(), app.getDockerInfo()
                                                                                        .getImage());
            }
            ApplicationToCreateDto applicationToCreateDto = ImmutableApplicationToCreateDto.builder()
                                                                                           .name(app.getName())
                                                                                           .staging(app.getStaging())
                                                                                           .diskQuotaInMb(diskQuotaInMb)
                                                                                           .memoryInMb(memoryInMb)
                                                                                           .metadata(app.getV3Metadata())
                                                                                           .routes(app.getRoutes())
                                                                                           .env(app.getEnv())
                                                                                           .build();
            client.createApplication(applicationToCreateDto);
            addMetadataAutoscalerLabel();
            context.setVariable(Variables.VCAP_APP_PROPERTIES_CHANGED, true);
            context.setVariable(Variables.USER_PROPERTIES_CHANGED, true);
        }

        @Override
        public void handleApplicationServices() {
            context.setVariable(Variables.SERVICES_TO_UNBIND_BIND, app.getServices());
        }

        @Override
        public void printStepStartMessage() {
            getStepLogger().info(Messages.CREATING_APP_FROM_MODULE, app.getName(), app.getModuleName());
        }

        @Override
        public void printStepEndMessage() {
            getStepLogger().debug(Messages.APP_CREATED, app.getName());
        }

        private void addMetadataAutoscalerLabel() {
            boolean shouldApplyIncrementalInstancesUpdate = context.getVariable(Variables.SHOULD_APPLY_INCREMENTAL_INSTANCES_UPDATE);
            if (shouldApplyIncrementalInstancesUpdate) {
                UUID applicationId = client.getApplicationGuid(app.getName());
                disableAutoscaling(context, client, applicationId);
            }
        }

    }

    private class UpdateAppFlowHandler extends StepFlowHandler {

        final CloudApplication existingApp;

        public UpdateAppFlowHandler(ProcessContext context, CloudControllerClient client, CloudApplicationExtended app,
                                    CloudApplication existingApp) {
            super(context, client, app);
            this.existingApp = existingApp;
        }

        @Override
        public void handleApplicationAttributes() {
            updateMetadata();

            List updateStates = getApplicationAttributeUpdaters().stream()
                                                                              .map(updater -> updater.update(existingApp, app))
                                                                              .collect(Collectors.toList());

            boolean arePropertiesChanged = updateStates.contains(UpdateState.UPDATED);

            reportApplicationUpdateStatus(app, arePropertiesChanged);
            context.setVariable(Variables.VCAP_APP_PROPERTIES_CHANGED, arePropertiesChanged);
            updateApplicationEnvironment();
        }

        private void updateApplicationEnvironment() {
            Map envAsMap = new LinkedHashMap<>(app.getEnv());
            var appEnv = client.getApplicationEnvironment(existingApp.getGuid());
            addCurrentAppDigestToNewEnv(envAsMap, appEnv);
            app = ImmutableCloudApplicationExtended.copyOf(app)
                                                   .withEnv(envAsMap);

            ControllerClientFacade.Context clientContext = new ControllerClientFacade.Context(client, context, getStepLogger());
            UpdateState updateApplicationEnvironmentState = new EnvironmentApplicationAttributeUpdater(clientContext,
                                                                                                       getEnvUpdateStrategy(),
                                                                                                       appEnv).update(existingApp, app);

            context.setVariable(Variables.USER_PROPERTIES_CHANGED, updateApplicationEnvironmentState == UpdateState.UPDATED);
        }

        private void updateMetadata() {
            if (app.getV3Metadata() == null) {
                return;
            }
            if (!Objects.equals(existingApp.getV3Metadata(), app.getV3Metadata())) {
                client.updateApplicationMetadata(existingApp.getGuid(), app.getV3Metadata());
            }
        }

        private void addCurrentAppDigestToNewEnv(Map newAppEnv, Map existingAppEnv) {
            String existingFileDigest = getExistingAppFileDigest(existingAppEnv);
            if (existingFileDigest == null) {
                return;
            }
            String newAppDeployAttributes = newAppEnv.get(org.cloudfoundry.multiapps.controller.core.Constants.ENV_DEPLOY_ATTRIBUTES);
            TreeMap newAppDeployAttributesMap = new TreeMap<>(JsonUtil.convertJsonToMap(newAppDeployAttributes));
            newAppDeployAttributesMap.put(org.cloudfoundry.multiapps.controller.core.Constants.ATTR_APP_CONTENT_DIGEST, existingFileDigest);
            newAppEnv.put(org.cloudfoundry.multiapps.controller.core.Constants.ENV_DEPLOY_ATTRIBUTES,
                          JsonUtil.toJson(newAppDeployAttributesMap, shouldPrettyPrint.getAsBoolean()));
        }

        private String getExistingAppFileDigest(Map envAsMap) {
            return new ApplicationFileDigestDetector(envAsMap).detectCurrentAppFileDigest();
        }

        private UpdateStrategy getEnvUpdateStrategy() {
            return app.getAttributesUpdateStrategy()
                      .shouldKeepExistingEnv() ? UpdateStrategy.MERGE : UpdateStrategy.REPLACE;
        }

        @Override
        public void handleApplicationServices() {
            if (context.getVariable(Variables.SHOULD_SKIP_SERVICE_REBINDING)) {
                return;
            }
            List services = getMtaAndExistingServices();
            context.setVariable(Variables.SERVICES_TO_UNBIND_BIND, services);
        }

        @Override
        public void printStepStartMessage() {
            getStepLogger().info(Messages.UPDATING_APP, app.getName());
        }

        @Override
        public void printStepEndMessage() {
            getStepLogger().debug(Messages.APP_UPDATED, app.getName());
        }

        private List getMtaAndExistingServices() {
            var serviceNamesGetter = getAppBoundServiceInstanceNamesGetter(context);
            return Stream.of(app.getServices(), serviceNamesGetter.getServiceInstanceNamesBoundToApp(existingApp.getGuid()))
                         .flatMap(List::stream)
                         .distinct()
                         .collect(Collectors.toList());
        }

        private void reportApplicationUpdateStatus(CloudApplicationExtended app, boolean appPropertiesChanged) {
            if (!appPropertiesChanged) {
                getStepLogger().info(Messages.APPLICATION_ATTRIBUTES_UNCHANGED, app.getName());
                return;
            }
            getStepLogger().debug(Messages.APP_UPDATED, app.getName());
        }

        protected List getApplicationAttributeUpdaters() {
            ControllerClientFacade.Context clientContext = new ControllerClientFacade.Context(client, context, getStepLogger());
            var process = client.getApplicationProcess(existingApp.getGuid());
            var currentRoutes = client.getApplicationRoutes(existingApp.getGuid());
            context.setVariable(Variables.CURRENT_ROUTES, currentRoutes);
            return List.of(new StagingApplicationAttributeUpdater(clientContext, process),
                           new MemoryApplicationAttributeUpdater(clientContext, process),
                           new DiskQuotaApplicationAttributeUpdater(clientContext, process),
                           new UrisApplicationAttributeUpdater(clientContext, UpdateStrategy.REPLACE, currentRoutes));
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy