Skip to content

Commit 0301f33

Browse files
committed
SCANJLIB-169 Rework the API to support functional errors
The bootstrapper now returns a result with a boolean allowing the caller to terminate without throwing an exception. This is used to log auth errors, but it may be also useful for other functional errors.
1 parent 13bf32d commit 0301f33

File tree

14 files changed

+347
-97
lines changed

14 files changed

+347
-97
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ 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);
209+
assertThat(buildResult.getLastStatus()).isNotZero();
210210
assertThat(buildResult.getLogs()).contains("Error status returned by url", ": 407");
211211
assertThat(seenByProxy).isEmpty();
212212

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ 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

223223
// different exception is thrown depending on the JDK version. See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8172163
224224
String failedAnalysis = "(?s).*java\\.lang\\.IllegalStateException: Failed to get server version.*";

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: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,17 @@
3333
import org.apache.commons.lang3.StringUtils;
3434
import org.slf4j.Logger;
3535
import org.slf4j.LoggerFactory;
36+
import org.sonarsource.scanner.lib.internal.FailedBootstrap;
3637
import org.sonarsource.scanner.lib.internal.InternalProperties;
38+
import org.sonarsource.scanner.lib.internal.SuccessfulBootstrap;
3739
import org.sonarsource.scanner.lib.internal.cache.FileCache;
3840
import org.sonarsource.scanner.lib.internal.facade.forked.NewScannerEngineFacade;
3941
import org.sonarsource.scanner.lib.internal.facade.forked.ScannerEngineLauncherFactory;
4042
import org.sonarsource.scanner.lib.internal.facade.inprocess.InProcessScannerEngineFacade;
4143
import org.sonarsource.scanner.lib.internal.facade.inprocess.IsolatedLauncherFactory;
4244
import org.sonarsource.scanner.lib.internal.facade.simulation.SimulationScannerEngineFacade;
4345
import org.sonarsource.scanner.lib.internal.http.HttpConfig;
46+
import org.sonarsource.scanner.lib.internal.http.HttpException;
4447
import org.sonarsource.scanner.lib.internal.http.ScannerHttpClient;
4548
import org.sonarsource.scanner.lib.internal.http.ssl.CertificateStore;
4649
import org.sonarsource.scanner.lib.internal.util.ArchResolver;
@@ -111,10 +114,7 @@ public ScannerEngineBootstrapper setBootstrapProperty(String key, String value)
111114
return this;
112115
}
113116

114-
/**
115-
* Bootstrap the scanner-engine.
116-
*/
117-
public ScannerEngineFacade bootstrap() {
117+
public ScannerEngineBootstrapResult bootstrap() {
118118
if (LOG.isDebugEnabled()) {
119119
LOG.debug("Scanner max available memory: {}", FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
120120
}
@@ -125,22 +125,52 @@ public ScannerEngineFacade bootstrap() {
125125
var isSimulation = immutableProperties.containsKey(InternalProperties.SCANNER_DUMP_TO_FILE);
126126
var sonarUserHome = resolveSonarUserHome(immutableProperties);
127127
var fileCache = FileCache.create(sonarUserHome);
128-
var httpConfig = new HttpConfig(immutableProperties, sonarUserHome);
129-
scannerHttpClient.init(httpConfig);
130-
String serverVersion = null;
131-
if (!isSonarCloud) {
132-
serverVersion = getServerVersion(scannerHttpClient, isSimulation, immutableProperties);
133-
}
134128

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

@@ -195,14 +225,14 @@ private static Path resolveSonarUserHome(Map<String, String> properties) {
195225
return Paths.get(sonarUserHome);
196226
}
197227

198-
private static String getServerVersion(ScannerHttpClient scannerHttpClient, boolean isSimulation, Map<String, String> properties) {
199-
if (isSimulation) {
200-
return properties.getOrDefault(InternalProperties.SCANNER_VERSION_SIMULATION, "5.6");
201-
}
202-
228+
private static String getServerVersion(ScannerHttpClient scannerHttpClient) {
203229
try {
204230
return scannerHttpClient.callRestApi("/analysis/version");
205231
} catch (Exception e) {
232+
if (e instanceof HttpException && (((HttpException) e).getCode() == 401 || ((HttpException) e).getCode() == 403)) {
233+
throw (HttpException) e;
234+
}
235+
206236
try {
207237
return scannerHttpClient.callWebApi("/api/server/version");
208238
} catch (Exception e2) {

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import java.util.Map;
2323

24-
2524
public interface ScannerEngineFacade extends AutoCloseable {
2625

2726
/**
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.internal;
21+
22+
import org.sonarsource.scanner.lib.ScannerEngineBootstrapResult;
23+
import org.sonarsource.scanner.lib.ScannerEngineFacade;
24+
25+
public class FailedBootstrap implements ScannerEngineBootstrapResult {
26+
27+
@Override
28+
public boolean isSuccessful() {
29+
return false;
30+
}
31+
32+
@Override
33+
public ScannerEngineFacade getEngineFacade() {
34+
throw new UnsupportedOperationException("No engine facade available");
35+
}
36+
37+
@Override
38+
public void close() {
39+
// No operation
40+
}
41+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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.internal;
21+
22+
import org.sonarsource.scanner.lib.ScannerEngineBootstrapResult;
23+
import org.sonarsource.scanner.lib.ScannerEngineFacade;
24+
25+
public class SuccessfulBootstrap implements ScannerEngineBootstrapResult {
26+
27+
private final ScannerEngineFacade facade;
28+
29+
public SuccessfulBootstrap(ScannerEngineFacade facade) {
30+
this.facade = facade;
31+
}
32+
33+
@Override
34+
public boolean isSuccessful() {
35+
return true;
36+
}
37+
38+
@Override
39+
public ScannerEngineFacade getEngineFacade() {
40+
return facade;
41+
}
42+
43+
@Override
44+
public void close() throws Exception {
45+
facade.close();
46+
}
47+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.internal.http;
21+
22+
import java.net.URL;
23+
import javax.annotation.Nullable;
24+
25+
public class HttpException extends RuntimeException {
26+
private final URL requestUrl;
27+
private final int code;
28+
@Nullable
29+
private final String body;
30+
31+
public HttpException(URL requestUrl, int code, String message, @Nullable String body) {
32+
super(message);
33+
this.requestUrl = requestUrl;
34+
this.code = code;
35+
this.body = body;
36+
}
37+
38+
public URL getRequestUrl() {
39+
return requestUrl;
40+
}
41+
42+
public int getCode() {
43+
return code;
44+
}
45+
46+
@Nullable
47+
public String getBody() {
48+
return body;
49+
}
50+
}

0 commit comments

Comments
 (0)