Skip to content

Commit f545139

Browse files
authored
SCANJLIB-169 Rework the API to support functional errors (#217)
1 parent f2a4581 commit f545139

File tree

70 files changed

+749
-278
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+749
-278
lines changed

its/it-simple-scanner/src/main/java/com/sonar/scanner/lib/it/Main.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121

2222
import java.util.HashMap;
2323
import java.util.Map;
24+
import java.util.concurrent.atomic.AtomicBoolean;
2425
import org.sonarsource.scanner.lib.EnvironmentConfig;
2526
import org.sonarsource.scanner.lib.ScannerEngineBootstrapper;
2627

2728
public class Main {
2829
public static void main(String[] args) {
30+
AtomicBoolean success = new AtomicBoolean(false);
2931
try {
3032

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

39-
runProject(props);
41+
success.set(runScanner(props));
4042
} catch (Exception e) {
4143
e.printStackTrace();
42-
System.exit(1);
44+
System.exit(2);
4345
}
44-
System.exit(0);
46+
System.exit(success.get() ? 0 : 1);
4547
}
4648

47-
private static void runProject(Map<String, String> props) throws Exception {
49+
private static boolean runScanner(Map<String, String> props) throws Exception {
4850

49-
try (var scannerEngine = ScannerEngineBootstrapper.create("Simple Scanner", "1.0")
51+
try (var bootstrapResult = ScannerEngineBootstrapper.create("Simple Scanner", "1.0")
5052
.addBootstrapProperties(props)
5153
.bootstrap()) {
52-
scannerEngine.analyze(props);
54+
if (bootstrapResult.isSuccessful()) {
55+
bootstrapResult.getEngineFacade().analyze(props);
56+
return true;
57+
} else {
58+
return false;
59+
}
5360
}
5461
}
5562
}

its/it-tests/src/test/java/com/sonar/scanner/lib/it/ProxyTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ public void simple_analysis_with_proxy_auth() throws Exception {
206206
params.put("sonar.scanner.proxyPort", "" + httpProxyPort);
207207

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

213213
params.put("sonar.scanner.proxyUser", PROXY_USER);

its/it-tests/src/test/java/com/sonar/scanner/lib/it/SSLTest.java

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ private static void startSSLTransparentReverseProxy(boolean requireClientAuth) t
115115

116116
// Handler Structure
117117
HandlerCollection handlers = new HandlerCollection();
118-
handlers.setHandlers(new Handler[] {proxyHandler(), new DefaultHandler()});
118+
handlers.setHandlers(new Handler[]{proxyHandler(), new DefaultHandler()});
119119
server.setHandler(handlers);
120120

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

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

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

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

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

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

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

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

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

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

266264
@DataProvider()
267265
public static Object[][] variousClientTrustStores() {
268-
return new Object[][] {
266+
return new Object[][]{
269267
{KEYSTORE_CLIENT_WITH_CA_KEYTOOL, CLIENT_WITH_CA_KEYSTORE_PASSWORD, true},
270268
{KEYSTORE_CLIENT_WITH_CA_OPENSSL, CLIENT_WITH_CA_KEYSTORE_PASSWORD, false},
271269
{KEYSTORE_CLIENT_WITH_CERTIFICATE_KEYTOOL, CLIENT_WITH_CERTIFICATE_KEYSTORE_PASSWORD, true},

lib/src/main/java/org/sonarsource/scanner/lib/EnvironmentConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class EnvironmentConfig {
4040
private static final String GENERIC_ENV_PREFIX = "SONAR_SCANNER_";
4141
private static final String SONAR_HOST_URL_ENV_VAR = "SONAR_HOST_URL";
4242
private static final String SONAR_USER_HOME_ENV_VAR = "SONAR_USER_HOME";
43-
private static final String TOKEN_ENV_VARIABLE = "SONAR_TOKEN";
43+
static final String TOKEN_ENV_VARIABLE = "SONAR_TOKEN";
4444

4545
private EnvironmentConfig() {
4646
// only static methods
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* SonarScanner Java Library
3+
* Copyright (C) 2011-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.scanner.lib;
21+
22+
/**
23+
* Closing this will automatically close the {@link ScannerEngineFacade} that it contains, if any.
24+
*/
25+
public interface ScannerEngineBootstrapResult extends AutoCloseable {
26+
27+
/**
28+
* Allow to test if the bootstrapping has been successful. If not, the {@link ScannerEngineFacade} should not be used.
29+
* A log message should have been emitted in case of failure.
30+
*
31+
* @return true if the bootstrapping has been successful, false otherwise
32+
*/
33+
boolean isSuccessful();
34+
35+
/**
36+
* Get the facade to interact with the engine. Only call this method if {@link #isSuccessful()} returns true.
37+
*
38+
* @return the facade to interact with the engine
39+
*/
40+
ScannerEngineFacade getEngineFacade();
41+
}

lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapper.java

Lines changed: 76 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,24 @@
3333
import org.apache.commons.lang3.StringUtils;
3434
import org.slf4j.Logger;
3535
import org.slf4j.LoggerFactory;
36-
import org.sonarsource.scanner.lib.internal.ArchResolver;
36+
import org.sonarsource.scanner.lib.internal.FailedBootstrap;
3737
import org.sonarsource.scanner.lib.internal.InternalProperties;
38-
import org.sonarsource.scanner.lib.internal.IsolatedLauncherFactory;
39-
import org.sonarsource.scanner.lib.internal.OsResolver;
40-
import org.sonarsource.scanner.lib.internal.Paths2;
41-
import org.sonarsource.scanner.lib.internal.ScannerEngineLauncherFactory;
38+
import org.sonarsource.scanner.lib.internal.MessageException;
39+
import org.sonarsource.scanner.lib.internal.SuccessfulBootstrap;
4240
import org.sonarsource.scanner.lib.internal.cache.FileCache;
41+
import org.sonarsource.scanner.lib.internal.facade.forked.NewScannerEngineFacade;
42+
import org.sonarsource.scanner.lib.internal.facade.forked.ScannerEngineLauncherFactory;
43+
import org.sonarsource.scanner.lib.internal.facade.inprocess.InProcessScannerEngineFacade;
44+
import org.sonarsource.scanner.lib.internal.facade.inprocess.IsolatedLauncherFactory;
45+
import org.sonarsource.scanner.lib.internal.facade.simulation.SimulationScannerEngineFacade;
4346
import org.sonarsource.scanner.lib.internal.http.HttpConfig;
47+
import org.sonarsource.scanner.lib.internal.http.HttpException;
4448
import org.sonarsource.scanner.lib.internal.http.ScannerHttpClient;
4549
import org.sonarsource.scanner.lib.internal.http.ssl.CertificateStore;
50+
import org.sonarsource.scanner.lib.internal.util.ArchResolver;
51+
import org.sonarsource.scanner.lib.internal.util.OsResolver;
52+
import org.sonarsource.scanner.lib.internal.util.Paths2;
53+
import org.sonarsource.scanner.lib.internal.util.System2;
4654
import org.sonarsource.scanner.lib.internal.util.VersionUtils;
4755

4856
import static java.util.Optional.ofNullable;
@@ -107,10 +115,7 @@ public ScannerEngineBootstrapper setBootstrapProperty(String key, String value)
107115
return this;
108116
}
109117

110-
/**
111-
* Bootstrap the scanner-engine.
112-
*/
113-
public ScannerEngineFacade bootstrap() {
118+
public ScannerEngineBootstrapResult bootstrap() {
114119
if (LOG.isDebugEnabled()) {
115120
LOG.debug("Scanner max available memory: {}", FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
116121
}
@@ -121,22 +126,59 @@ public ScannerEngineFacade bootstrap() {
121126
var isSimulation = immutableProperties.containsKey(InternalProperties.SCANNER_DUMP_TO_FILE);
122127
var sonarUserHome = resolveSonarUserHome(immutableProperties);
123128
var fileCache = FileCache.create(sonarUserHome);
124-
var httpConfig = new HttpConfig(immutableProperties, sonarUserHome);
125-
scannerHttpClient.init(httpConfig);
126-
String serverVersion = null;
127-
if (!isSonarCloud) {
128-
serverVersion = getServerVersion(scannerHttpClient, isSimulation, immutableProperties);
129-
}
130129

131130
if (isSimulation) {
132-
return new SimulationScannerEngineFacade(immutableProperties, isSonarCloud, serverVersion);
133-
} else if (isSonarCloud || VersionUtils.isAtLeastIgnoringQualifier(serverVersion, SQ_VERSION_NEW_BOOTSTRAPPING)) {
134-
var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, immutableProperties);
135-
return new NewScannerEngineFacade(immutableProperties, launcher, isSonarCloud, serverVersion);
131+
var serverVersion = immutableProperties.getOrDefault(InternalProperties.SCANNER_VERSION_SIMULATION, "9.9");
132+
return new SuccessfulBootstrap(new SimulationScannerEngineFacade(immutableProperties, isSonarCloud, serverVersion));
133+
}
134+
135+
// No HTTP call should be made before this point
136+
try {
137+
var httpConfig = new HttpConfig(immutableProperties, sonarUserHome);
138+
scannerHttpClient.init(httpConfig);
139+
140+
var serverVersion = !isSonarCloud ? getServerVersion(scannerHttpClient) : null;
141+
if (isSonarCloud || VersionUtils.isAtLeastIgnoringQualifier(serverVersion, SQ_VERSION_NEW_BOOTSTRAPPING)) {
142+
var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, immutableProperties);
143+
return new SuccessfulBootstrap(new NewScannerEngineFacade(immutableProperties, launcher, isSonarCloud, serverVersion));
144+
} else {
145+
var launcher = launcherFactory.createLauncher(scannerHttpClient, fileCache);
146+
var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(immutableProperties, httpConfig);
147+
return new SuccessfulBootstrap(new InProcessScannerEngineFacade(adaptedProperties, launcher, false, serverVersion));
148+
}
149+
} catch (MessageException e) {
150+
return handleException(e);
151+
}
152+
}
153+
154+
private static ScannerEngineBootstrapResult handleException(MessageException e) {
155+
var message = new StringBuilder(e.getMessage());
156+
if (e.getCause() instanceof HttpException) {
157+
var httpEx = (HttpException) e.getCause();
158+
var code = httpEx.getCode();
159+
if (code == 401 || code == 403) {
160+
var helpMessage = "Please check the property " + ScannerProperties.SONAR_TOKEN +
161+
" or the environment variable " + EnvironmentConfig.TOKEN_ENV_VARIABLE + ".";
162+
message.append(". ").append(helpMessage);
163+
}
164+
if (code == 407) {
165+
var helpMessage = "Please check the properties " + ScannerProperties.SONAR_SCANNER_PROXY_USER +
166+
" and " + ScannerProperties.SONAR_SCANNER_PROXY_PASSWORD + ".";
167+
message.append(". ").append(helpMessage);
168+
}
169+
}
170+
logWithStacktraceOnlyIfDebug(message.toString(), e);
171+
return new FailedBootstrap();
172+
}
173+
174+
/**
175+
* For functional errors, the stacktrace is not necessary. It is only useful for debugging.
176+
*/
177+
private static void logWithStacktraceOnlyIfDebug(String message, Throwable t) {
178+
if (LOG.isDebugEnabled()) {
179+
LOG.error(message, t);
136180
} else {
137-
var launcher = launcherFactory.createLauncher(scannerHttpClient, fileCache);
138-
var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(immutableProperties, httpConfig);
139-
return new InProcessScannerEngineFacade(adaptedProperties, launcher, false, serverVersion);
181+
LOG.error(message);
140182
}
141183
}
142184

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

194-
private static String getServerVersion(ScannerHttpClient scannerHttpClient, boolean isSimulation, Map<String, String> properties) {
195-
if (isSimulation) {
196-
return properties.getOrDefault(InternalProperties.SCANNER_VERSION_SIMULATION, "5.6");
197-
}
198-
236+
private static String getServerVersion(ScannerHttpClient scannerHttpClient) {
199237
try {
200238
return scannerHttpClient.callRestApi("/analysis/version");
201239
} catch (Exception e) {
202-
try {
203-
return scannerHttpClient.callWebApi("/api/server/version");
204-
} catch (Exception e2) {
205-
var ex = new IllegalStateException("Failed to get server version", e2);
206-
ex.addSuppressed(e);
207-
throw ex;
240+
if (e instanceof HttpException && ((HttpException) e).getCode() == 404) {
241+
// Fallback to the old endpoint
242+
try {
243+
return scannerHttpClient.callWebApi("/api/server/version");
244+
} catch (Exception e2) {
245+
var ex = new MessageException("Failed to query server version: " + e2.getMessage(), e2);
246+
ex.addSuppressed(e);
247+
throw ex;
248+
}
249+
} else {
250+
throw new MessageException("Failed to query server version: " + e.getMessage(), e);
208251
}
209252
}
210253
}

0 commit comments

Comments
 (0)