com.sap.cds.framework.spring.utils.Deploy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cds-framework-spring-boot Show documentation
Show all versions of cds-framework-spring-boot Show documentation
Integration into the Spring Boot framework for CDS Services Java
/**************************************************************************
* (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;
}
}
}