Skip to content

Commit 42f22ef

Browse files
committed
SCANJLIB-216 Rework loading of env variables
1 parent 7bfe22f commit 42f22ef

13 files changed

Lines changed: 300 additions & 162 deletions

File tree

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,35 @@
2121

2222
import java.util.HashMap;
2323
import java.util.Map;
24+
import org.sonarsource.scanner.lib.EnvironmentConfig;
2425
import org.sonarsource.scanner.lib.LogOutput;
2526
import org.sonarsource.scanner.lib.ScannerEngineBootstrapper;
2627
import org.sonarsource.scanner.lib.StdOutLogOutput;
2728

2829
public class Main {
2930
public static void main(String[] args) {
31+
LogOutput logOutput = new StdOutLogOutput();
3032
try {
3133
Map<String, String> props = new HashMap<>();
34+
35+
props.putAll(EnvironmentConfig.load(logOutput));
36+
3237
for (String k : System.getProperties().stringPropertyNames()) {
3338
if (k.startsWith("sonar.")) {
3439
props.put(k, System.getProperty(k));
3540
}
3641
}
3742

38-
runProject(props);
43+
runProject(props, logOutput);
3944
} catch (Exception e) {
4045
e.printStackTrace();
4146
System.exit(1);
4247
}
4348
System.exit(0);
4449
}
4550

46-
private static void runProject(Map<String, String> props) throws Exception {
47-
LogOutput logOutput = new StdOutLogOutput();
51+
private static void runProject(Map<String, String> props, LogOutput logOutput) throws Exception {
52+
4853
try (var scannerEngine = new ScannerEngineBootstrapper("Simple Scanner", "1.0", logOutput)
4954
.addBootstrapProperties(props)
5055
.bootstrap()) {

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,25 @@ public class PropertiesTest {
4141
public void testRuntimeEnvironmentPassedAsUserAgent() throws IOException {
4242
SimpleScanner scanner = new SimpleScanner();
4343
Map<String, String> params = new HashMap<>();
44-
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params);
45-
assertThat(buildResult.getLastStatus()).isEqualTo(0);
44+
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params, Map.of());
45+
assertThat(buildResult.getLastStatus()).isZero();
46+
assertThat(buildResult.getLogs()).contains("2 files indexed");
4647

4748
Path accessLogs = ORCHESTRATOR.getServer().getAppLogs().toPath().resolveSibling("access.log");
4849
String accessLogsContent = new String(Files.readAllBytes(accessLogs));
4950
assertThat(accessLogsContent).contains("\"Simple Scanner/1.0\"");
5051
}
5152

53+
@Test
54+
public void passConfigurationUsingEnvVariables() throws IOException {
55+
SimpleScanner scanner = new SimpleScanner();
56+
Map<String, String> params = new HashMap<>();
57+
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params, Map.of("SONAR_SCANNER_JSON_PARAMS", "{\"sonar.exclusions\": \"**/Hello.js\"}"));
58+
assertThat(buildResult.getLastStatus()).isZero();
59+
60+
assertThat(buildResult.getLogs()).contains("1 file indexed");
61+
}
62+
5263
private static Path project(String projectName) {
5364
return Paths.get("..", "projects", projectName);
5465
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public void simple_analysis_with_proxy_no_auth() throws Exception {
191191
params.put("http.proxyHost", "localhost");
192192
params.put("http.proxyPort", "" + httpProxyPort);
193193

194-
buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params);
194+
buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params, Map.of());
195195
assertThat(buildResult.getLastStatus()).isZero();
196196
assertThat(seenByProxy).isNotEmpty();
197197
}
@@ -207,14 +207,14 @@ public void simple_analysis_with_proxy_auth() throws Exception {
207207
params.put("http.proxyHost", "localhost");
208208
params.put("http.proxyPort", "" + httpProxyPort);
209209

210-
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params);
210+
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params, Map.of());
211211
assertThat(buildResult.getLastStatus()).isEqualTo(1);
212212
assertThat(buildResult.getLogs()).contains("Status returned by url", "is not valid: [407]");
213213
assertThat(seenByProxy).isEmpty();
214214

215215
params.put("http.proxyUser", PROXY_USER);
216216
params.put("http.proxyPassword", PROXY_PASSWORD);
217-
buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params);
217+
buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params, Map.of());
218218
assertThat(seenByProxy).isNotEmpty();
219219
if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(6, 1)) {
220220
assertThat(buildResult.getLastStatus()).isZero();

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ private static void startSSLTransparentReverseProxy(boolean requireClientAuth) t
106106

107107
// Handler Structure
108108
HandlerCollection handlers = new HandlerCollection();
109-
handlers.setHandlers(new Handler[] {proxyHandler(), new DefaultHandler()});
109+
handlers.setHandlers(new Handler[]{proxyHandler(), new DefaultHandler()});
110110
server.setHandler(handlers);
111111

112112
ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
@@ -184,7 +184,7 @@ public void simple_analysis_with_server_and_client_certificate() throws Exceptio
184184
params.put("javax.net.ssl.keyStore", clientKeystore.toString());
185185
params.put("javax.net.ssl.keyStorePassword", CLIENT_KEYSTORE_PASSWORD);
186186

187-
buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params);
187+
buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params, Map.of());
188188
assertThat(buildResult.getLastStatus()).isZero();
189189
}
190190

@@ -208,7 +208,7 @@ public void simple_analysis_with_server_and_without_client_certificate_is_failin
208208
params.put("javax.net.ssl.trustStorePassword", CLIENT_WITH_CA_KEYSTORE_PASSWORD);
209209
// Voluntary missing client keystore
210210

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

214214
// different exception is thrown depending on the JDK version. See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8172163
@@ -251,7 +251,7 @@ private void simple_analysis_with_server_certificate(String clientTrustStore, St
251251
params.put("javax.net.ssl.trustStore", clientTruststore.toString());
252252
params.put("javax.net.ssl.trustStorePassword", keyStorePassword);
253253

254-
buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params);
254+
buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params, Map.of());
255255
assertThat(buildResult.getLastStatus()).isZero();
256256
}
257257
}

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,24 @@
2525
import java.time.Instant;
2626
import java.time.ZoneId;
2727
import java.time.format.DateTimeFormatter;
28-
import org.apache.commons.lang.StringUtils;
2928
import org.junit.ClassRule;
3029
import org.junit.runner.RunWith;
3130
import org.junit.runners.Suite;
3231
import org.junit.runners.Suite.SuiteClasses;
3332

34-
import static org.assertj.core.api.Assertions.fail;
35-
3633
@RunWith(Suite.class)
3734
@SuiteClasses({ProxyTest.class, SSLTest.class, PropertiesTest.class})
3835
public class ScannerJavaLibraryTestSuite {
3936
private static final String SONAR_RUNTIME_VERSION = "sonar.runtimeVersion";
4037

4138
@ClassRule
4239
public static final OrchestratorRule ORCHESTRATOR = OrchestratorRule.builderEnv()
43-
.setSonarVersion(getSystemPropertyOrFail(SONAR_RUNTIME_VERSION))
40+
.setSonarVersion(System.getProperty(SONAR_RUNTIME_VERSION, "LATEST_RELEASE"))
4441
.useDefaultAdminCredentialsForBuilds(true)
4542
// We need to use a plugin compatible with both SonarQube DEV & SonarQube version defined in .cirrus.yml (currently SQ 7.9)
4643
.addPlugin(MavenLocation.of("org.sonarsource.javascript", "sonar-javascript-plugin", "7.4.4.15624"))
4744
.build();
4845

49-
private static String getSystemPropertyOrFail(String orchestratorPropertiesSource) {
50-
String propertyValue = System.getProperty(orchestratorPropertiesSource);
51-
if (StringUtils.isEmpty(propertyValue)) {
52-
fail(orchestratorPropertiesSource + " system property must be defined");
53-
}
54-
return propertyValue;
55-
}
56-
5746
public static void resetData(OrchestratorRule orchestrator) {
5847
Instant instant = Instant.now();
5948

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,17 @@
1919
*/
2020
package com.sonar.scanner.lib.it.tools;
2121

22+
import com.sonar.orchestrator.build.BuildResult;
2223
import com.sonar.scanner.lib.it.ScannerJavaLibraryTestSuite;
2324
import java.io.IOException;
2425
import java.nio.file.Files;
2526
import java.nio.file.Path;
2627
import java.nio.file.Paths;
2728
import java.util.ArrayList;
28-
import java.util.Collections;
2929
import java.util.List;
3030
import java.util.Map;
3131
import java.util.Properties;
3232

33-
import com.sonar.orchestrator.build.BuildResult;
34-
3533
public class SimpleScanner {
3634
private static final Path JAR_PATH = Paths.get("..", "it-simple-scanner", "target", "simple-scanner.jar")
3735
.toAbsolutePath()
@@ -52,18 +50,18 @@ public SimpleScanner() {
5250
}
5351

5452
public BuildResult executeSimpleProject(Path baseDir, String host) throws IOException {
55-
return executeSimpleProject(baseDir, host, Collections.emptyMap());
53+
return executeSimpleProject(baseDir, host, Map.of(), Map.of());
5654
}
5755

58-
public BuildResult executeSimpleProject(Path baseDir, String host, Map<String, String> extraProps) throws IOException {
56+
public BuildResult executeSimpleProject(Path baseDir, String host, Map<String, String> extraProps, Map<String, String> env) throws IOException {
5957
List<String> params = new ArrayList<>();
6058
Map<String, String> props = getSimpleProjectProperties(baseDir, host, extraProps);
6159

6260
props.forEach((k, v) -> params.add("-D" + k + "=" + v));
6361
params.add("-jar");
6462
params.add(JAR_PATH.toString());
6563

66-
int status = exec.execute(params.toArray(new String[params.size()]));
64+
int status = exec.execute(params.toArray(new String[params.size()]), env);
6765
BuildResult result = new BuildResult();
6866

6967
result.addStatus(status);

lib/pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
<artifactId>sonar-scanner-java-library-batch-interface</artifactId>
4141
<version>${project.version}</version>
4242
</dependency>
43+
<dependency>
44+
<groupId>org.apache.commons</groupId>
45+
<artifactId>commons-lang3</artifactId>
46+
</dependency>
4347

4448
<!-- unit tests -->
4549
<dependency>
@@ -67,11 +71,6 @@
6771
<artifactId>mockwebserver</artifactId>
6872
<scope>test</scope>
6973
</dependency>
70-
<dependency>
71-
<groupId>org.apache.commons</groupId>
72-
<artifactId>commons-lang3</artifactId>
73-
<scope>test</scope>
74-
</dependency>
7574
<dependency>
7675
<!-- used to compare results -->
7776
<groupId>commons-codec</groupId>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
import com.google.gson.Gson;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
import java.util.Optional;
26+
import java.util.stream.Stream;
27+
import org.apache.commons.lang3.StringUtils;
28+
29+
/**
30+
* Utility class to load configuration from environment variables.
31+
*/
32+
public class EnvironmentConfig {
33+
34+
private static final String SONAR_SCANNER_JSON_PARAMS = "SONAR_SCANNER_JSON_PARAMS";
35+
private static final String SONARQUBE_SCANNER_PARAMS = "SONARQUBE_SCANNER_PARAMS";
36+
private static final String GENERIC_ENV_PREFIX = "SONAR_SCANNER_";
37+
private static final String SONAR_HOST_URL_ENV_VAR = "SONAR_HOST_URL";
38+
39+
private EnvironmentConfig() {
40+
// only static methods
41+
}
42+
43+
public static Map<String, String> load(LogOutput logger) {
44+
return load(System.getenv(), logger);
45+
}
46+
47+
static Map<String, String> load(Map<String, String> env, LogOutput logger) {
48+
var loadedProps = new HashMap<String, String>();
49+
Optional.ofNullable(env.get(SONAR_HOST_URL_ENV_VAR)).ifPresent(url -> loadedProps.put(ScannerProperties.HOST_URL, url));
50+
env.forEach((key, value) -> {
51+
if (!key.equals(SONAR_SCANNER_JSON_PARAMS) && key.startsWith(GENERIC_ENV_PREFIX)) {
52+
processEnvVariable(key, value, loadedProps, logger);
53+
}
54+
});
55+
var jsonParams = env.get(SONAR_SCANNER_JSON_PARAMS);
56+
var oldJsonParams = env.get(SONARQUBE_SCANNER_PARAMS);
57+
if (jsonParams != null) {
58+
if (oldJsonParams != null && !oldJsonParams.equals(jsonParams)) {
59+
logger.log("Ignoring environment variable '" + SONARQUBE_SCANNER_PARAMS + "' because '" + SONAR_SCANNER_JSON_PARAMS + "' is set", LogOutput.Level.WARN);
60+
}
61+
parseJsonPropertiesFromEnv(jsonParams, loadedProps, SONAR_SCANNER_JSON_PARAMS, logger);
62+
} else if (oldJsonParams != null) {
63+
parseJsonPropertiesFromEnv(oldJsonParams, loadedProps, SONARQUBE_SCANNER_PARAMS, logger);
64+
}
65+
return loadedProps;
66+
}
67+
68+
private static void parseJsonPropertiesFromEnv(String jsonParams, Map<String, String> inputProperties, String envVariableName, LogOutput logger) {
69+
try {
70+
var jsonProperties = new Gson().<Map<String, String>>fromJson(jsonParams, Map.class);
71+
if (jsonProperties != null) {
72+
jsonProperties.forEach((key, value) -> {
73+
if (inputProperties.containsKey(key)) {
74+
if (!inputProperties.get(key).equals(value)) {
75+
logger.log("Ignoring property '" + key + "' from env variable '" + envVariableName + "' because it is already defined", LogOutput.Level.WARN);
76+
}
77+
} else {
78+
inputProperties.put(key, value);
79+
}
80+
});
81+
}
82+
} catch (Exception e) {
83+
throw new IllegalArgumentException("Failed to parse JSON properties from environment variable '" + envVariableName + "'", e);
84+
}
85+
}
86+
87+
private static void processEnvVariable(String key, String value, Map<String, String> inputProperties, LogOutput logger) {
88+
var suffix = key.substring(GENERIC_ENV_PREFIX.length());
89+
if (suffix.isEmpty()) {
90+
return;
91+
}
92+
var toCamelCase = Stream.of(suffix.split("_"))
93+
.map(String::toLowerCase)
94+
.reduce((a, b) -> a + StringUtils.capitalize(b)).orElseThrow();
95+
var propKey = "sonar.scanner." + toCamelCase;
96+
if (inputProperties.containsKey(propKey)) {
97+
if (!inputProperties.get(propKey).equals(value)) {
98+
logger.log("Ignoring environment variable '" + key + "' because it is already defined in the properties", LogOutput.Level.WARN);
99+
}
100+
} else {
101+
inputProperties.put(propKey, value);
102+
}
103+
}
104+
105+
}

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

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,42 +27,24 @@
2727
import org.sonarsource.scanner.lib.internal.ClassloadRules;
2828
import org.sonarsource.scanner.lib.internal.InternalProperties;
2929
import org.sonarsource.scanner.lib.internal.IsolatedLauncherFactory;
30-
import org.sonarsource.scanner.lib.internal.cache.Logger;
3130

3231
/**
3332
* Entry point to run a Sonar analysis programmatically.
3433
*/
3534
public class ScannerEngineBootstrapper {
36-
private static final String SONAR_HOST_URL_ENV_VAR = "SONAR_HOST_URL";
35+
3736
private static final String SONARCLOUD_HOST = "https://sonarcloud.io";
3837
private final IsolatedLauncherFactory launcherFactory;
3938
private final LogOutput logOutput;
4039
private final Map<String, String> bootstrapProperties = new HashMap<>();
41-
private final System2 system;
4240

4341
public ScannerEngineBootstrapper(String app, String version, final LogOutput logOutput) {
44-
this(app, version, logOutput, new System2());
45-
}
46-
47-
/**
48-
* For testing.
49-
*/
50-
public ScannerEngineBootstrapper(String app, String version, final LogOutput logOutput, System2 system2) {
51-
this(new LoggerAdapter(logOutput), logOutput, system2);
42+
this.logOutput = logOutput;
43+
this.launcherFactory = new IsolatedLauncherFactory(new LoggerAdapter(logOutput));
5244
this.setBootstrapProperty(InternalProperties.SCANNER_APP, app)
5345
.setBootstrapProperty(InternalProperties.SCANNER_APP_VERSION, version);
5446
}
5547

56-
private ScannerEngineBootstrapper(Logger logger, LogOutput logOutput, System2 system) {
57-
this(logOutput, system, new IsolatedLauncherFactory(logger));
58-
}
59-
60-
ScannerEngineBootstrapper(LogOutput logOutput, System2 system, IsolatedLauncherFactory launcherFactory) {
61-
this.launcherFactory = launcherFactory;
62-
this.logOutput = logOutput;
63-
this.system = system;
64-
}
65-
6648
/**
6749
* Declare technical properties needed to bootstrap (sonar.host.url, credentials, proxy, ...).
6850
*/
@@ -94,12 +76,7 @@ public ScannerEngineFacade bootstrap() {
9476
}
9577

9678
private void initBootstrapDefaultValues() {
97-
String sonarHostUrl = system.getEnvironmentVariable(SONAR_HOST_URL_ENV_VAR);
98-
if (sonarHostUrl != null) {
99-
setBootstrapPropertyIfNotAlreadySet(ScannerProperties.HOST_URL, sonarHostUrl);
100-
} else {
101-
setBootstrapPropertyIfNotAlreadySet(ScannerProperties.HOST_URL, getSonarCloudUrl());
102-
}
79+
setBootstrapPropertyIfNotAlreadySet(ScannerProperties.HOST_URL, getSonarCloudUrl());
10380
}
10481

10582
private String getSonarCloudUrl() {

0 commit comments

Comments
 (0)