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

com.sap.cds.framework.spring.utils.Deploy Maven / Gradle / Ivy

There is a newer version: 3.2.0
Show newest version
/**************************************************************************
 * (C) 2019-2021 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.framework.spring.utils;

import java.io.IOException;
import java.util.Arrays;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sap.cds.mtx.impl.Authenticator;
import com.sap.cds.mtx.impl.ClientCredentialJwtAccess;
import com.sap.cds.mtx.impl.ClientCredentialJwtReader;
import com.sap.cds.services.environment.CdsProperties.MultiTenancy;
import com.sap.cds.services.mt.MtSubscriptionService;
import com.sap.cds.services.runtime.CdsRuntime;
import com.sap.cds.services.utils.mtx.MtxUtils;
import com.sap.cloud.mt.subscription.json.SidecarUpgradePayload;

/**
 * Class providing a main method to update the database content.
 * This main method starts the whole server. Therefore, all background tasks that might be registered will also start.
 * If this could lead to problems, they need to be deactivated.
 */
@SpringBootApplication
public class Deploy {

	private static final Logger log = LoggerFactory.getLogger(Deploy.class);
	private static final ObjectMapper mapper = new ObjectMapper();

	private static final String PARAMETER_ALL_TENANTS = "all";

	public static void main(String[] args) throws Exception {
		try {
			// deactivate authentication and messaging
			System.setProperty("cds.security.xsuaa.enabled", "false");
			System.setProperty("cds.security.mock.enabled", "false");
			System.setProperty("cds.messaging.webhooks.enabled", "false");

			// start without web server
			CdsRuntime runtime = new SpringApplicationBuilder(Deploy.class).web(WebApplicationType.NONE).run(new String[0]).getBean(CdsRuntime.class);
			MtSubscriptionService mtService = runtime.getServiceCatalog().getService(MtSubscriptionService.class, MtSubscriptionService.DEFAULT_NAME);
			// make sure the property names above stay correct
			assert !runtime.getEnvironment().getCdsProperties().getSecurity().getXsuaa().isEnabled();
			assert !runtime.getEnvironment().getCdsProperties().getSecurity().getMock().isEnabled();
			assert !runtime.getEnvironment().getCdsProperties().getMessaging().getWebhooks().isEnabled();

			if (mtService != null) {
				long timestamp = System.currentTimeMillis();
				String[] tenants = Arrays.copyOf(args, args.length);
				if (tenants.length == 0) {
					tenants = new String[] {PARAMETER_ALL_TENANTS};
					log.info("Starting database update for all tenants");
				} else {
					log.info("Starting database update for tenant(s) {}", String.join(", ", tenants));
				}
				DeployHelper deployHelper = new DeployHelper(mtService, runtime);
				if (!deployHelper.deploy(tenants)) {
					logErrorMessage();
					System.exit(3);
				}
				log.info("Database update finished successfully in {}s", (System.currentTimeMillis() - timestamp)/1000);
			} else {
				log.error("Failed: MT Service not found");
				logErrorMessage();
				System.exit(2);
			}
		} catch (Throwable t) {
			log.error("Unexpected error", t);
			logErrorMessage();
			System.exit(1);
		}
		logSuccessMessage();
		System.exit(0);
	}

	private static void logSuccessMessage() {
		log.info("*************");
		log.info("*  SUCCESS  *");
		log.info("*************");
	}

	private static void logErrorMessage() {
		log.error("***********");
		log.error("*  ERROR  *");
		log.error("***********");
	}

	public static class DeployHelper {
		private static final String SIDECAR_STATUS_RUNNING = "RUNNING";
		private static final String SIDECAR_DEPLOY_RESULT_SUCCESS = "SUCCESS";

		private final MtSubscriptionService mtService;
		private final CdsRuntime runtime;

		public DeployHelper(MtSubscriptionService mtService, CdsRuntime runtime) {
			this.mtService = mtService;
			this.runtime = runtime;
		}

		public boolean deploy(String[] tenants) {
			SidecarUpgradePayload upgradePayload = new SidecarUpgradePayload();
			upgradePayload.tenants = tenants;

			MultiTenancy config = runtime.getEnvironment().getCdsProperties().getMultiTenancy();
			String deployScope = config.getSecurity().getDeploymentScope();
			return runtime.requestContext().clearUser().modifyUser(user -> user.addRole(deployScope).setIsAuthenticated(true)).run(context -> {
				MtxUtils mtxUtils = new MtxUtils(runtime);
				if (mtxUtils.mtxEnabled()) {
					try {
						// obtains a JWT token through client credential flow
						ClientCredentialJwtReader jwtReader = mtxUtils.createClientCredentialJwtReader();
						Authenticator jwtAccess = jwtReader != null ? new ClientCredentialJwtAccess(jwtReader) : Authenticator.NONE;
						String jobId = startDbDeploymentSidecar(upgradePayload, jwtAccess);
						SidecarResult sidecarResult = waitForDeploymentToFinish(jobId, jwtAccess);
						boolean deploymentFailed = sidecarResult.result.tenants.values().stream().anyMatch(tenant -> !SIDECAR_DEPLOY_RESULT_SUCCESS.equals(tenant.status));
						if (sidecarResult.error != null || deploymentFailed) {
							log.error("Database update failed, last sidecar response:\n{}", mapper.writerWithDefaultPrettyPrinter().writeValueAsString(sidecarResult));
							return false;
						}
					} catch (Throwable t) {
						throw new RuntimeException(t);
					}
				} else {
					mtService.deploy(upgradePayload);
				}
				return true;
			});

		}

		private String startDbDeploymentSidecar(SidecarUpgradePayload upgradePayload,
				Authenticator authenticator) throws IOException {
			String jobIdResultStr = mtService.asyncDeploy(upgradePayload, authenticator.getAuthorization().orElse(null));
			JobIdResult jobIdResult = mapper.readValue(jobIdResultStr, JobIdResult.class);
			return jobIdResult.jobID;
		}

		private SidecarResult waitForDeploymentToFinish(String jobId, Authenticator authenticator)
				throws InterruptedException, IOException {
			String resultStr;
			String status;
			SidecarResult sidecarResult;

			long lastTimestamp = 0;

			do {

				Thread.sleep(2000);

				resultStr = mtService.asyncDeployStatus(jobId, authenticator.getAuthorization().orElse(null));
				// print out status once a minute
				if (System.currentTimeMillis() - lastTimestamp > 60000) {
					log.info("Waiting for database update to finish. Current status: {}", resultStr);
					lastTimestamp = System.currentTimeMillis();
				}
				sidecarResult = mapper.readValue(resultStr, SidecarResult.class);
				status = sidecarResult.status;
			} while (SIDECAR_STATUS_RUNNING.equals(status));
			log.debug("Last sidecar response: {}", resultStr);
			return sidecarResult;
		}

		@JsonIgnoreProperties(ignoreUnknown = true)
		private static final class SidecarResult {
			public String error;
			public String status;
			public DeployResult result;

			private static final class DeployResult {
				public Map tenants;

				private static final class TenantResult {
					public String status;
					@SuppressWarnings("unused")
					public String message;
					@SuppressWarnings("unused")
					public String buildLogs;
				}
			}
		}

		@JsonIgnoreProperties(ignoreUnknown = true)
		private static final class JobIdResult {
			public String jobID;
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy