Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SCANJLIB-169 Rework the API to support functional errors #217

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.sonarsource.scanner.lib.EnvironmentConfig;
import org.sonarsource.scanner.lib.ScannerEngineBootstrapper;

public class Main {
public static void main(String[] args) {
AtomicBoolean success = new AtomicBoolean(false);
try {

Map<String, String> props = new HashMap<>(EnvironmentConfig.load());
Expand All @@ -36,20 +38,25 @@ public static void main(String[] args) {
}
}

runProject(props);
success.set(runScanner(props));
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
System.exit(2);
}
System.exit(0);
System.exit(success.get() ? 0 : 1);
}

private static void runProject(Map<String, String> props) throws Exception {
private static boolean runScanner(Map<String, String> props) throws Exception {

try (var scannerEngine = ScannerEngineBootstrapper.create("Simple Scanner", "1.0")
try (var bootstrapResult = ScannerEngineBootstrapper.create("Simple Scanner", "1.0")
.addBootstrapProperties(props)
.bootstrap()) {
scannerEngine.analyze(props);
if (bootstrapResult.isSuccessful()) {
bootstrapResult.getEngineFacade().analyze(props);
return true;
} else {
return false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ public void simple_analysis_with_proxy_auth() throws Exception {
params.put("sonar.scanner.proxyPort", "" + httpProxyPort);

BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params, Map.of());
assertThat(buildResult.getLastStatus()).isEqualTo(1);
assertThat(buildResult.getLogs()).contains("Error status returned by url", ": 407");
assertThat(buildResult.getLastStatus()).isNotZero();
assertThat(buildResult.getLogs()).contains("Failed to query server version: Proxy Authentication Required.");
assertThat(seenByProxy).isEmpty();

params.put("sonar.scanner.proxyUser", PROXY_USER);
Expand Down
24 changes: 11 additions & 13 deletions its/it-tests/src/test/java/com/sonar/scanner/lib/it/SSLTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private static void startSSLTransparentReverseProxy(boolean requireClientAuth) t

// Handler Structure
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] {proxyHandler(), new DefaultHandler()});
handlers.setHandlers(new Handler[]{proxyHandler(), new DefaultHandler()});
server.setHandler(handlers);

ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
Expand Down Expand Up @@ -178,7 +178,7 @@ public void simple_analysis_with_server_and_client_certificate() throws Exceptio
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort);

assertThat(buildResult.getLastStatus()).isNotZero();
assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException");
assertThat(buildResult.getLogs()).contains("None of the TrustManagers trust this certificate chain");

Path clientTruststore = Paths.get(SSLTest.class.getResource(KEYSTORE_CLIENT_WITH_CA_KEYTOOL).toURI()).toAbsolutePath();
assertThat(clientTruststore).exists();
Expand All @@ -204,7 +204,7 @@ public void simple_analysis_with_server_and_without_client_certificate_is_failin
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort);

assertThat(buildResult.getLastStatus()).isNotZero();
assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException");
assertThat(buildResult.getLogs()).contains("None of the TrustManagers trust this certificate chain");

Path clientTruststore = Paths.get(SSLTest.class.getResource(KEYSTORE_CLIENT_WITH_CA_KEYTOOL).toURI()).toAbsolutePath();
assertThat(clientTruststore).exists();
Expand All @@ -218,16 +218,14 @@ public void simple_analysis_with_server_and_without_client_certificate_is_failin
// Voluntary missing client keystore

buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params, Map.of());
assertThat(buildResult.getLastStatus()).isEqualTo(1);
assertThat(buildResult.getLastStatus()).isNotZero();

// different exception is thrown depending on the JDK version. See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8172163
String failedAnalysis = "(?s).*java\\.lang\\.IllegalStateException: Failed to get server version.*";
var commonMessage = "Failed to query server version: Call to URL [https://localhost:" + httpsPort + "/api/v2/analysis/version] failed: ";
assertThat(buildResult.getLogs())
.matches(p -> p.matches(failedAnalysis + "Caused by: javax\\.net\\.ssl\\.SSLException: Broken pipe \\(Write failed\\).*") ||
p.matches(failedAnalysis + "Caused by: javax\\.net\\.ssl\\.SSLProtocolException: Broken pipe \\(Write failed\\).*") ||
p.matches(failedAnalysis + "Caused by: javax\\.net\\.ssl\\.SSLHandshakeException: Received fatal alert: bad_certificate.*") ||
p.matches(failedAnalysis + "Caused by: java\\.net\\.SocketException: Broken pipe.*") ||
p.matches(failedAnalysis + "Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target.*"));
.containsAnyOf(
// different exception is thrown depending on the JDK version. See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8172163
commonMessage + "Received fatal alert: bad_certificate",
commonMessage + "Broken pipe");
}

private static Path project(String projectName) {
Expand All @@ -244,7 +242,7 @@ public void simple_analysis_with_server_certificate(String clientTrustStore, Str

BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort);
assertThat(buildResult.getLastStatus()).isNotZero();
assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException");
assertThat(buildResult.getLogs()).contains("None of the TrustManagers trust this certificate chain");

Path clientTruststore = Paths.get(SSLTest.class.getResource(clientTrustStore).toURI()).toAbsolutePath();
assertThat(clientTruststore).exists();
Expand All @@ -265,7 +263,7 @@ public void simple_analysis_with_server_certificate(String clientTrustStore, Str

@DataProvider()
public static Object[][] variousClientTrustStores() {
return new Object[][] {
return new Object[][]{
{KEYSTORE_CLIENT_WITH_CA_KEYTOOL, CLIENT_WITH_CA_KEYSTORE_PASSWORD, true},
{KEYSTORE_CLIENT_WITH_CA_OPENSSL, CLIENT_WITH_CA_KEYSTORE_PASSWORD, false},
{KEYSTORE_CLIENT_WITH_CERTIFICATE_KEYTOOL, CLIENT_WITH_CERTIFICATE_KEYSTORE_PASSWORD, true},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class EnvironmentConfig {
private static final String GENERIC_ENV_PREFIX = "SONAR_SCANNER_";
private static final String SONAR_HOST_URL_ENV_VAR = "SONAR_HOST_URL";
private static final String SONAR_USER_HOME_ENV_VAR = "SONAR_USER_HOME";
private static final String TOKEN_ENV_VARIABLE = "SONAR_TOKEN";
static final String TOKEN_ENV_VARIABLE = "SONAR_TOKEN";

private EnvironmentConfig() {
// only static methods
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SonarScanner Java Library
* Copyright (C) 2011-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarsource.scanner.lib;

/**
* Closing this will automatically close the {@link ScannerEngineFacade} that it contains, if any.
*/
public interface ScannerEngineBootstrapResult extends AutoCloseable {

/**
* Allow to test if the bootstrapping has been successful. If not, the {@link ScannerEngineFacade} should not be used.
* A log message should have been emitted in case of failure.
*
* @return true if the bootstrapping has been successful, false otherwise
*/
boolean isSuccessful();

/**
* Get the facade to interact with the engine. Only call this method if {@link #isSuccessful()} returns true.
*
* @return the facade to interact with the engine
*/
ScannerEngineFacade getEngineFacade();
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,24 @@
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonarsource.scanner.lib.internal.ArchResolver;
import org.sonarsource.scanner.lib.internal.FailedBootstrap;
import org.sonarsource.scanner.lib.internal.InternalProperties;
import org.sonarsource.scanner.lib.internal.IsolatedLauncherFactory;
import org.sonarsource.scanner.lib.internal.OsResolver;
import org.sonarsource.scanner.lib.internal.Paths2;
import org.sonarsource.scanner.lib.internal.ScannerEngineLauncherFactory;
import org.sonarsource.scanner.lib.internal.MessageException;
import org.sonarsource.scanner.lib.internal.SuccessfulBootstrap;
import org.sonarsource.scanner.lib.internal.cache.FileCache;
import org.sonarsource.scanner.lib.internal.facade.forked.NewScannerEngineFacade;
import org.sonarsource.scanner.lib.internal.facade.forked.ScannerEngineLauncherFactory;
import org.sonarsource.scanner.lib.internal.facade.inprocess.InProcessScannerEngineFacade;
import org.sonarsource.scanner.lib.internal.facade.inprocess.IsolatedLauncherFactory;
import org.sonarsource.scanner.lib.internal.facade.simulation.SimulationScannerEngineFacade;
import org.sonarsource.scanner.lib.internal.http.HttpConfig;
import org.sonarsource.scanner.lib.internal.http.HttpException;
import org.sonarsource.scanner.lib.internal.http.ScannerHttpClient;
import org.sonarsource.scanner.lib.internal.http.ssl.CertificateStore;
import org.sonarsource.scanner.lib.internal.util.ArchResolver;
import org.sonarsource.scanner.lib.internal.util.OsResolver;
import org.sonarsource.scanner.lib.internal.util.Paths2;
import org.sonarsource.scanner.lib.internal.util.System2;
import org.sonarsource.scanner.lib.internal.util.VersionUtils;

import static java.util.Optional.ofNullable;
Expand Down Expand Up @@ -107,10 +115,7 @@ public ScannerEngineBootstrapper setBootstrapProperty(String key, String value)
return this;
}

/**
* Bootstrap the scanner-engine.
*/
public ScannerEngineFacade bootstrap() {
public ScannerEngineBootstrapResult bootstrap() {
if (LOG.isDebugEnabled()) {
LOG.debug("Scanner max available memory: {}", FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
}
Expand All @@ -121,22 +126,59 @@ public ScannerEngineFacade bootstrap() {
var isSimulation = immutableProperties.containsKey(InternalProperties.SCANNER_DUMP_TO_FILE);
var sonarUserHome = resolveSonarUserHome(immutableProperties);
var fileCache = FileCache.create(sonarUserHome);
var httpConfig = new HttpConfig(immutableProperties, sonarUserHome);
scannerHttpClient.init(httpConfig);
String serverVersion = null;
if (!isSonarCloud) {
serverVersion = getServerVersion(scannerHttpClient, isSimulation, immutableProperties);
}

if (isSimulation) {
return new SimulationScannerEngineFacade(immutableProperties, isSonarCloud, serverVersion);
} else if (isSonarCloud || VersionUtils.isAtLeastIgnoringQualifier(serverVersion, SQ_VERSION_NEW_BOOTSTRAPPING)) {
var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, immutableProperties);
return new NewScannerEngineFacade(immutableProperties, launcher, isSonarCloud, serverVersion);
var serverVersion = immutableProperties.getOrDefault(InternalProperties.SCANNER_VERSION_SIMULATION, "9.9");
return new SuccessfulBootstrap(new SimulationScannerEngineFacade(immutableProperties, isSonarCloud, serverVersion));
}

// No HTTP call should be made before this point
try {
var httpConfig = new HttpConfig(immutableProperties, sonarUserHome);
scannerHttpClient.init(httpConfig);

var serverVersion = !isSonarCloud ? getServerVersion(scannerHttpClient) : null;
if (isSonarCloud || VersionUtils.isAtLeastIgnoringQualifier(serverVersion, SQ_VERSION_NEW_BOOTSTRAPPING)) {
var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, immutableProperties);
return new SuccessfulBootstrap(new NewScannerEngineFacade(immutableProperties, launcher, isSonarCloud, serverVersion));
} else {
var launcher = launcherFactory.createLauncher(scannerHttpClient, fileCache);
var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(immutableProperties, httpConfig);
return new SuccessfulBootstrap(new InProcessScannerEngineFacade(adaptedProperties, launcher, false, serverVersion));
}
} catch (MessageException e) {
return handleException(e);
}
}

private static ScannerEngineBootstrapResult handleException(MessageException e) {
var message = new StringBuilder(e.getMessage());
if (e.getCause() instanceof HttpException) {
var httpEx = (HttpException) e.getCause();
var code = httpEx.getCode();
if (code == 401 || code == 403) {
var helpMessage = "Please check the property " + ScannerProperties.SONAR_TOKEN +
" or the environment variable " + EnvironmentConfig.TOKEN_ENV_VARIABLE + ".";
message.append(". ").append(helpMessage);
}
if (code == 407) {
var helpMessage = "Please check the properties " + ScannerProperties.SONAR_SCANNER_PROXY_USER +
" and " + ScannerProperties.SONAR_SCANNER_PROXY_PASSWORD + ".";
message.append(". ").append(helpMessage);
}
}
logWithStacktraceOnlyIfDebug(message.toString(), e);
return new FailedBootstrap();
}

/**
* For functional errors, the stacktrace is not necessary. It is only useful for debugging.
*/
private static void logWithStacktraceOnlyIfDebug(String message, Throwable t) {
if (LOG.isDebugEnabled()) {
LOG.error(message, t);
} else {
var launcher = launcherFactory.createLauncher(scannerHttpClient, fileCache);
var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(immutableProperties, httpConfig);
return new InProcessScannerEngineFacade(adaptedProperties, launcher, false, serverVersion);
LOG.error(message);
}
}

Expand Down Expand Up @@ -191,20 +233,21 @@ private static Path resolveSonarUserHome(Map<String, String> properties) {
return Paths.get(sonarUserHome);
}

private static String getServerVersion(ScannerHttpClient scannerHttpClient, boolean isSimulation, Map<String, String> properties) {
if (isSimulation) {
return properties.getOrDefault(InternalProperties.SCANNER_VERSION_SIMULATION, "5.6");
}

private static String getServerVersion(ScannerHttpClient scannerHttpClient) {
try {
return scannerHttpClient.callRestApi("/analysis/version");
} catch (Exception e) {
try {
return scannerHttpClient.callWebApi("/api/server/version");
} catch (Exception e2) {
var ex = new IllegalStateException("Failed to get server version", e2);
ex.addSuppressed(e);
throw ex;
if (e instanceof HttpException && ((HttpException) e).getCode() == 404) {
// Fallback to the old endpoint
try {
return scannerHttpClient.callWebApi("/api/server/version");
} catch (Exception e2) {
var ex = new MessageException("Failed to query server version: " + e2.getMessage(), e2);
ex.addSuppressed(e);
throw ex;
}
} else {
throw new MessageException("Failed to query server version: " + e.getMessage(), e);
}
}
}
Expand Down
Loading
Loading