Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hp.octane.integrations.services.sonar.SonarServiceImpl Maven / Gradle / Ivy
/**
* Copyright 2017-2023 Open Text
*
* The only warranties for products and services of Open Text and
* its affiliates and licensors (“Open Text”) are as may be set forth
* in the express warranty statements accompanying such products and services.
* Nothing herein should be construed as constituting an additional warranty.
* Open Text shall not be liable for technical or editorial errors or
* omissions contained herein. The information contained herein is subject
* to change without notice.
*
* Except as specifically indicated otherwise, this document contains
* confidential information and a valid license is required for possession,
* use or copying. If this work is provided to the U.S. Government,
* consistent with FAR 12.211 and 12.212, Commercial Computer Software,
* Computer Software Documentation, and Technical Data for Commercial Items are
* licensed to the U.S. Government under vendor's standard commercial license.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hp.octane.integrations.services.sonar;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.hp.octane.integrations.OctaneSDK;
import com.hp.octane.integrations.dto.DTOFactory;
import com.hp.octane.integrations.dto.connectivity.OctaneResponse;
import com.hp.octane.integrations.dto.coverage.BuildCoverage;
import com.hp.octane.integrations.dto.coverage.CoverageReportType;
import com.hp.octane.integrations.exceptions.PermanentException;
import com.hp.octane.integrations.exceptions.SonarIntegrationException;
import com.hp.octane.integrations.exceptions.TemporaryException;
import com.hp.octane.integrations.services.WorkerPreflight;
import com.hp.octane.integrations.services.configuration.ConfigurationService;
import com.hp.octane.integrations.services.configuration.ConfigurationServiceImpl;
import com.hp.octane.integrations.services.coverage.CoverageService;
import com.hp.octane.integrations.services.queueing.QueueingService;
import com.hp.octane.integrations.utils.CIPluginSDKUtils;
import com.squareup.tape.ObjectQueue;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* Default implementations of Sonar service
*/
//TODO to move worker to coverage service and make queue item generic
public class SonarServiceImpl implements SonarService {
private static final Logger logger = LogManager.getLogger(SonarServiceImpl.class);
private static final DTOFactory dtoFactory = DTOFactory.getInstance();
private static final String SONAR_COVERAGE_QUEUE_FILE = "sonar-coverage-queue.dat";
private static final String WEBHOOK_CREATE_URI = "/api/webhooks/create";
private static final String WEBHOOK_LIST_URI = "/api/webhooks/list";
private static final String SONAR_STATUS_URI = "/api/system/status";
private static final String CONNECTION_FAILURE = "CONNECTION_FAILURE";
private static final String COMPONENT_TREE_URI = "/api/measures/component_tree";
private final ExecutorService sonarIntegrationExecutor = Executors.newSingleThreadExecutor(new SonarIntegrationWorkerThreadFactory());
private final ObjectQueue sonarIntegrationQueue;
private final OctaneSDK.SDKServicesConfigurer configurer;
private final CoverageService coverageService;
private final WorkerPreflight workerPreflight;
private final ConfigurationService configurationService;
private int TEMPORARY_ERROR_BREATHE_INTERVAL = 15000;
public SonarServiceImpl(OctaneSDK.SDKServicesConfigurer configurer, QueueingService queueingService, CoverageService coverageService, ConfigurationService configurationService) {
if (configurer == null || configurer.pluginServices == null || configurer.octaneConfiguration == null) {
throw new IllegalArgumentException("invalid configurer");
}
if (queueingService == null) {
throw new IllegalArgumentException("queue service MUST NOT be null");
}
if (coverageService == null) {
throw new IllegalArgumentException("coverage service MUST NOT be null");
}
this.configurer = configurer;
this.coverageService = coverageService;
this.configurationService = configurationService;
this.workerPreflight = new WorkerPreflight(this, configurationService, logger);
if (queueingService.isPersistenceEnabled()) {
sonarIntegrationQueue = queueingService.initFileQueue(SONAR_COVERAGE_QUEUE_FILE, SonarBuildCoverageQueueItem.class);
} else {
sonarIntegrationQueue = queueingService.initMemoQueue();
}
logger.info(configurer.octaneConfiguration.getLocationForLog() + "starting background worker...");
sonarIntegrationExecutor.execute(this::worker);
logger.info(configurer.octaneConfiguration.getLocationForLog() + "initialized SUCCESSFULLY (backed by " + sonarIntegrationQueue.getClass().getSimpleName() + ")");
}
// infallible everlasting background worker
private void worker() {
while (!sonarIntegrationExecutor.isShutdown()) {
if(!workerPreflight.preflight()){
continue;
}
SonarBuildCoverageQueueItem sonarBuildCoverageQueueItem = null;
try {
sonarBuildCoverageQueueItem = sonarIntegrationQueue.peek();
retrieveAndPushSonarDataToOctane(sonarBuildCoverageQueueItem);
logger.debug(configurer.octaneConfiguration.getLocationForLog() + "successfully processed " + sonarBuildCoverageQueueItem);
sonarIntegrationQueue.remove();
} catch (TemporaryException te) {
logger.error(configurer.octaneConfiguration.getLocationForLog() + "temporary error on " + sonarBuildCoverageQueueItem + ", breathing " + TEMPORARY_ERROR_BREATHE_INTERVAL + "ms and retrying", te);
CIPluginSDKUtils.doWait(TEMPORARY_ERROR_BREATHE_INTERVAL);
} catch (PermanentException pe) {
logger.error(configurer.octaneConfiguration.getLocationForLog() + "permanent error on " + sonarBuildCoverageQueueItem + ", passing over", pe);
sonarIntegrationQueue.remove();
} catch (Throwable t) {
logger.error(configurer.octaneConfiguration.getLocationForLog() + "unexpected error on build coverage item '" + sonarBuildCoverageQueueItem + "', passing over", t);
sonarIntegrationQueue.remove();
}
}
}
@Override
public synchronized void ensureSonarWebhookExist(String ciCallbackUrl, String sonarURL, String sonarToken) throws SonarIntegrationException {
//problem in sonar project key in new project
try {
String webhookKey = getWebhookKey(ciCallbackUrl, sonarURL, sonarToken);
if (webhookKey == null) {
HttpClient httpClient = HttpClientBuilder.create().build();
String name = configurer.pluginServices.getServerInfo().getType() + "-" + configurer.pluginServices.getServerInfo().getUrl()
.replaceAll("[<>:\"/\\|?*]", "_").trim();
URIBuilder uriBuilder = new URIBuilder(sonarURL + WEBHOOK_CREATE_URI)
.setParameter("name", name)
.setParameter("url", ciCallbackUrl);
HttpPost request = new HttpPost(uriBuilder.toString());
setTokenInHttpRequest(request, sonarToken);
HttpResponse response = httpClient.execute(request);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
// error can sometimes return empty results
String errorMessage = "exception during webhook registration for ciNotificationUrl: "
.concat(ciCallbackUrl)
.concat(" with status code: ")
.concat(String.valueOf(response.getStatusLine().getStatusCode()));
throw new SonarIntegrationException(errorMessage);
}
}
} catch (SonarIntegrationException e) {
logger.error(configurer.octaneConfiguration.getLocationForLog() + e.getMessage(), e);
throw e;
} catch (Exception e) {
String errorMessage = "exception during webhook registration for ciNotificationUrl: " + ciCallbackUrl;
logger.error(configurer.octaneConfiguration.getLocationForLog() + errorMessage, e);
throw new SonarIntegrationException(errorMessage, e);
}
}
@Override
public void shutdown() {
sonarIntegrationExecutor.shutdown();
}
@Override
public boolean isShutdown() {
return sonarIntegrationExecutor.isShutdown();
}
@Override
public void enqueueFetchAndPushSonarCoverage(String jobId, String buildId, String projectKey, String sonarURL, String sonarToken, String rootJobId) {
if (jobId == null || jobId.isEmpty()) {
throw new IllegalArgumentException("job ID MUST NOT be null nor empty");
}
if (buildId == null || buildId.isEmpty()) {
throw new IllegalArgumentException("build ID MUST NOT be null nor empty");
}
if (sonarURL == null || sonarURL.isEmpty()) {
throw new IllegalArgumentException("sonar URL MUST NOT be null nor empty");
}
if (this.configurer.octaneConfiguration.isDisabled()) {
return;
}
if (!((ConfigurationServiceImpl) configurationService).isRelevantForOctane(rootJobId)) {
return;
}
sonarIntegrationQueue.add(new SonarBuildCoverageQueueItem(jobId, buildId, projectKey, sonarURL, sonarToken));
workerPreflight.itemAddedToQueue();
}
@Override
public String getSonarStatus(String sonarURL) {
try {
URIBuilder uriBuilder = new URIBuilder(sonarURL + SONAR_STATUS_URI);
HttpClient httpClient = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(uriBuilder.build());
HttpResponse response = httpClient.execute(request);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return CIPluginSDKUtils.getObjectMapper().readTree(response.getEntity().getContent()).get("status").textValue();
} else {
return CONNECTION_FAILURE;
}
} catch (URISyntaxException | IOException e) {
return CONNECTION_FAILURE;
}
}
private void retrieveAndPushSonarDataToOctane(SonarBuildCoverageQueueItem queueItem) {
// preflight
if (!coverageService.isSonarReportRelevant(queueItem.jobId)) {
return;
}
StringBuilder errorMessage = new StringBuilder()
.append("failed to inject sonarqube coverage data to octane for project key: ")
.append(queueItem.projectKey)
.append(" with ciIdentity: ").append(configurer.octaneConfiguration.getInstanceId())
.append(" with jobId: ").append(queueItem.jobId)
.append(" with buildId: ").append(queueItem.buildId);
try {
// retrieve coverage report from Sonar
Integer pageIndex = 0;
BuildCoverage buildCoverageReport = dtoFactory.newDTO(BuildCoverage.class);
JsonNode jsonReport;
do {
pageIndex++;
InputStream reportStream = getPageFromSonar(queueItem, pageIndex);
jsonReport = CIPluginSDKUtils.getObjectMapper().readTree(reportStream);
buildCoverageReport.mergeSonarCoverageReport(jsonReport);
} while (SonarUtils.sonarReportHasAnotherPage(pageIndex, jsonReport));
// push coverage to Octane
OctaneResponse response = coverageService.pushCoverage(queueItem.jobId, queueItem.buildId, CoverageReportType.SONAR_REPORT, dtoFactory.dtoToJsonStream(buildCoverageReport));
if (response.getStatus() == HttpStatus.SC_SERVICE_UNAVAILABLE) {
errorMessage.append(" with status code: ").append(response.getStatus());
throw new TemporaryException(errorMessage.toString());
} else if (response.getStatus() != HttpStatus.SC_OK) {
errorMessage.append(" with status code: ").append(response.getStatus())
.append(" and response body: ").append(response.getBody());
throw new PermanentException(errorMessage.toString());
}
} catch (Throwable throwable) {
logger.error(configurer.octaneConfiguration.getLocationForLog() + errorMessage.toString(), throwable);
throw new PermanentException(throwable);
}
}
private String getWebhookKey(String ciNotificationUrl, String sonarURL, String token) throws SonarIntegrationException {
try {
URIBuilder uriBuilder = new URIBuilder(sonarURL + WEBHOOK_LIST_URI);
HttpClient httpClient = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(uriBuilder.build());
setTokenInHttpRequest(request, token);
HttpResponse response = httpClient.execute(request);
InputStream content = response.getEntity().getContent();
// if webhooks exist
if (content.available() != 0) {
JsonNode jsonResponse = CIPluginSDKUtils.getObjectMapper().readTree(content);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
ArrayNode webhooksListJson = (ArrayNode) jsonResponse.get("webhooks");
if (webhooksListJson.size() > 0) {
for (JsonNode webhookNode : webhooksListJson) {
String entryURL = webhookNode.get("url").textValue();
if (entryURL.equals(ciNotificationUrl)) {
return webhookNode.get("key").textValue();
}
}
}
return null;
} else {
String errorMessage = ""
.concat("failed to get webhook key from sonarqube with notification URL: ")
.concat(ciNotificationUrl)
.concat(" with status code: ").concat(String.valueOf(response.getStatusLine().getStatusCode()))
.concat(" with errors: ").concat(jsonResponse.get("errors").toString());
throw new SonarIntegrationException(errorMessage);
}
}
return null;
} catch (SonarIntegrationException e) {
logger.error(configurer.octaneConfiguration.getLocationForLog() + e.getMessage(), e);
throw e;
} catch (Exception e) {
String errorMessage = ""
.concat("failed to get webhook key from sonarqube with notification URL: ").concat(ciNotificationUrl);
logger.error(configurer.octaneConfiguration.getLocationForLog() + errorMessage, e);
throw new SonarIntegrationException(errorMessage, e);
}
}
private InputStream getPageFromSonar(SonarBuildCoverageQueueItem queueItem, Integer page) {
String sonarURL = queueItem.sonarURL;
String projectKey = queueItem.projectKey;
String token = queueItem.sonarToken;
StringBuilder errorMessage = new StringBuilder()
.append("failed to get data from sonar for project key: ")
.append(projectKey);
try {
URIBuilder uriBuilder = new URIBuilder(sonarURL + COMPONENT_TREE_URI);
uriBuilder.setParameter("metricKeys", "lines_to_cover,uncovered_lines")
.setParameter("component", projectKey)
.setParameter("qualifiers", "FIL,TRK")
.setParameter("ps", "500")
.setParameter("p", page.toString());
HttpClient httpClient = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(uriBuilder.build());
setTokenInHttpRequest(request, token);
HttpResponse httpResponse = httpClient.execute(request);
int statusCode = httpResponse.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
errorMessage.append(" with status code: ").append(statusCode)
.append(" and response body: ").append(EntityUtils.toString(httpResponse.getEntity(), "UTF-8"));
throw new PermanentException(errorMessage.toString());
} else {
return httpResponse.getEntity().getContent();
}
} catch (PermanentException e) {
throw e;
} catch (Exception e) {
logger.error(configurer.octaneConfiguration.getLocationForLog() + errorMessage.toString(), e);
throw new PermanentException(errorMessage.toString(), e);
}
}
private void setTokenInHttpRequest(HttpRequest request, String token) throws AuthenticationException {
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(token, "");
request.addHeader(new BasicScheme().authenticate(creds, request, null));
}
@Override
public long getQueueSize() {
return sonarIntegrationQueue.size();
}
@Override
public void clearQueue() {
while (sonarIntegrationQueue.size() > 0) {
sonarIntegrationQueue.remove();
}
}
@Override
public Map getMetrics() {
Map map = new LinkedHashMap<>();
map.put("queueSize", this.getQueueSize());
workerPreflight.addMetrics(map);
return map;
}
private static final class SonarBuildCoverageQueueItem implements QueueingService.QueueItem {
private String jobId;
private String buildId;
private String projectKey;
private String sonarURL;
private String sonarToken;
// [YG] this constructor MUST be present, don't remove
private SonarBuildCoverageQueueItem() {
}
public SonarBuildCoverageQueueItem(String jobId, String buildId, String projectKey, String sonarURL, String sonarToken) {
this.jobId = jobId;
this.buildId = buildId;
this.projectKey = projectKey;
this.sonarURL = sonarURL;
this.sonarToken = sonarToken;
}
@Override
public String toString() {
return "'" + jobId + " #" + buildId + "'";
}
}
private static final class SonarIntegrationWorkerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable runnable) {
Thread result = new Thread(runnable);
result.setName("SonarIntegrationWorker-" + result.getId());
result.setDaemon(true);
return result;
}
}
}