Pārlūkot izejas kodu

SONAR-22086 Add support to configure scanner with env variables

pull/3361/head
Julien HENRY pirms 1 mēnesi
vecāks
revīzija
7a68e52509

+ 2
- 0
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/Batch.java Parādīt failu

@@ -26,6 +26,7 @@ import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.sonar.api.utils.MessageException;
import org.sonar.scanner.bootstrap.EnvironmentConfig;
import org.sonar.scanner.bootstrap.SpringGlobalContainer;

/**
@@ -47,6 +48,7 @@ public final class Batch {
}
if (builder.globalProperties != null) {
globalProperties.putAll(builder.globalProperties);
EnvironmentConfig.processEnvVariables(globalProperties);
}
if (builder.isEnableLoggingConfiguration()) {
loggingConfig = new LoggingConfiguration(builder.environment).setProperties(globalProperties);

+ 99
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/EnvironmentConfig.java Parādīt failu

@@ -0,0 +1,99 @@
/*
* SonarQube
* Copyright (C) 2009-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.sonar.scanner.bootstrap;

import com.google.gson.Gson;
import java.util.Map;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;

public class EnvironmentConfig {

private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(EnvironmentConfig.class);

private static final String SONAR_SCANNER_JSON_PARAMS = "SONAR_SCANNER_JSON_PARAMS";
private static final String SONARQUBE_SCANNER_PARAMS = "SONARQUBE_SCANNER_PARAMS";
private static final String GENERIC_ENV_PREFIX = "SONAR_SCANNER_";

private EnvironmentConfig() {
// only static methods
}

public static void processEnvVariables(Map<String, String> inputProperties) {
processEnvVariables(inputProperties, System.getenv());
}

static void processEnvVariables(Map<String, String> inputProperties, Map<String, String> env) {
env.forEach((key, value) -> {
if (!key.equals(SONAR_SCANNER_JSON_PARAMS) && key.startsWith(GENERIC_ENV_PREFIX)) {
processEnvVariable(key, value, inputProperties);
}
});
var jsonParams = env.get(SONAR_SCANNER_JSON_PARAMS);
var oldJsonParams = env.get(SONARQUBE_SCANNER_PARAMS);
if (jsonParams != null) {
if (oldJsonParams != null && !oldJsonParams.equals(jsonParams)) {
LOG.warn("Ignoring environment variable '{}' because '{}' is set", SONARQUBE_SCANNER_PARAMS, SONAR_SCANNER_JSON_PARAMS);
}
parseJsonPropertiesFromEnv(jsonParams, inputProperties, SONAR_SCANNER_JSON_PARAMS);
} else if (oldJsonParams != null) {
parseJsonPropertiesFromEnv(oldJsonParams, inputProperties, SONARQUBE_SCANNER_PARAMS);
}
}

private static void parseJsonPropertiesFromEnv(String jsonParams, Map<String, String> inputProperties, String envVariableName) {
try {
var jsonProperties = new Gson().<Map<String, String>>fromJson(jsonParams, Map.class);
if (jsonProperties != null) {
jsonProperties.forEach((key, value) -> {
if (inputProperties.containsKey(key)) {
if (!inputProperties.get(key).equals(value)) {
LOG.warn("Ignoring property '{}' from env variable '{}' because it is already defined", key, envVariableName);
}
} else {
inputProperties.put(key, value);
}
});
}
} catch (Exception e) {
throw new IllegalArgumentException("Failed to parse JSON properties from environment variable '" + envVariableName + "'", e);
}
}

private static void processEnvVariable(String key, String value, Map<String, String> inputProperties) {
var suffix = key.substring(GENERIC_ENV_PREFIX.length());
if (suffix.isEmpty()) {
return;
}
var toCamelCase = Stream.of(suffix.split("_"))
.map(String::toLowerCase)
.reduce((a, b) -> a + StringUtils.capitalize(b)).orElseThrow();
var propKey = "sonar.scanner." + toCamelCase;
if (inputProperties.containsKey(propKey)) {
if (!inputProperties.get(propKey).equals(value)) {
LOG.warn("Ignoring environment variable '{}' because it is already defined in the properties", key);
}
} else {
inputProperties.put(propKey, value);
}
}

}

+ 2
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java Parādīt failu

@@ -60,6 +60,8 @@ public class ScannerMain {

var properties = parseInputProperties(in);

EnvironmentConfig.processEnvVariables(properties);

configureLogLevel(properties);

runScannerEngine(properties);

+ 170
- 0
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/EnvironmentConfigTest.java Parādīt failu

@@ -0,0 +1,170 @@
/*
* SonarQube
* Copyright (C) 2009-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.sonar.scanner.bootstrap;

import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.event.Level;
import org.sonar.api.testfixtures.log.LogTesterJUnit5;

import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;

class EnvironmentConfigTest {

@RegisterExtension
private final LogTesterJUnit5 logTester = new LogTesterJUnit5();

@Test
void shouldProcessGenericEnvVariables() {
var inputProperties = new HashMap<String, String>();
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of("SONAR_SCANNER", "ignored",
"SONAR_SCANNER_", "ignored as well",
"SONAR_SCANNER_FOO", "bar",
"SONAR_SCANNER_FOO_BAZ", "bar",
"SONAR_SCANNER_fuZz_bAz", "env vars are case insensitive"));

assertThat(inputProperties).containsOnly(
entry("sonar.scanner.foo", "bar"),
entry("sonar.scanner.fooBaz", "bar"),
entry("sonar.scanner.fuzzBaz", "env vars are case insensitive"));
}

@Test
void genericEnvVarShouldNotOverrideInputProperties() {
var inputProperties = new HashMap<String, String>(Map.of("sonar.scanner.foo", "foo", "sonar.scanner.bar", "same value"));
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of(
"SONAR_SCANNER_FOO", "should not override",
"SONAR_SCANNER_BAR", "same value",
"SONAR_SCANNER_BAZ", "baz"));

assertThat(inputProperties).containsOnly(
entry("sonar.scanner.foo", "foo"),
entry("sonar.scanner.bar", "same value"),
entry("sonar.scanner.baz", "baz"));

assertThat(logTester.logs(Level.WARN)).containsOnly("Ignoring environment variable 'SONAR_SCANNER_FOO' because it is already defined in the properties");
}

@Test
void shouldProcessJsonEnvVariables() {
var inputProperties = new HashMap<String, String>();
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of("SONAR_SCANNER_JSON_PARAMS",
"{\"key1\":\"value1\", \"key2\":\"value2\"}"));

assertThat(inputProperties).containsOnly(
entry("key1", "value1"),
entry("key2", "value2"));
}

@Test
void ignoreEmptyValueForJsonEnv() {
var inputProperties = new HashMap<String, String>();
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of("SONAR_SCANNER_JSON_PARAMS", ""));

assertThat(inputProperties).isEmpty();
}

@Test
void throwIfInvalidFormat() {
var inputProperties = new HashMap<String, String>();
var env = Map.of("SONAR_SCANNER_JSON_PARAMS", "{garbage");
var thrown = assertThrows(IllegalArgumentException.class, () -> EnvironmentConfig.processEnvVariables(inputProperties, env));

assertThat(thrown).hasMessage("Failed to parse JSON properties from environment variable 'SONAR_SCANNER_JSON_PARAMS'");
}

@Test
void jsonEnvVariablesShouldNotOverrideInputProperties() {
var inputProperties = new HashMap<String, String>(Map.of("key1", "value1", "key3", "value3"));
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of("SONAR_SCANNER_JSON_PARAMS",
"{\"key1\":\"should not override\", \"key2\":\"value2\"}"));

assertThat(inputProperties).containsOnly(
entry("key1", "value1"),
entry("key2", "value2"),
entry("key3", "value3"));

assertThat(logTester.logs(Level.WARN)).containsOnly("Ignoring property 'key1' from env variable 'SONAR_SCANNER_JSON_PARAMS' because it is already defined");
}

@Test
void jsonEnvVariablesShouldNotOverrideGenericEnv() {
var inputProperties = new HashMap<String, String>();
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of("SONAR_SCANNER_FOO", "value1",
"SONAR_SCANNER_JSON_PARAMS", "{\"sonar.scanner.foo\":\"should not override\", \"key2\":\"value2\"}"));

assertThat(inputProperties).containsOnly(
entry("sonar.scanner.foo", "value1"),
entry("key2", "value2"));

assertThat(logTester.logs(Level.WARN)).containsOnly("Ignoring property 'sonar.scanner.foo' from env variable 'SONAR_SCANNER_JSON_PARAMS' because it is already defined");
}

@Test
void shouldProcessOldJsonEnvVariables() {
var inputProperties = new HashMap<String, String>();
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of("SONARQUBE_SCANNER_PARAMS",
"{\"key1\":\"value1\", \"key2\":\"value2\"}"));

assertThat(inputProperties).containsOnly(
entry("key1", "value1"),
entry("key2", "value2"));
}

@Test
void oldJsonEnvVariablesIsIgnoredIfNewIsDefinedAndLogAWarning() {
var inputProperties = new HashMap<String, String>();
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of("SONARQUBE_SCANNER_PARAMS", "{\"key1\":\"should not override\", \"key3\":\"value3\"}",
"SONAR_SCANNER_JSON_PARAMS", "{\"key1\":\"value1\", \"key2\":\"value2\"}"));

assertThat(inputProperties).containsOnly(
entry("key1", "value1"),
entry("key2", "value2"));

assertThat(logTester.logs(Level.WARN)).containsOnly("Ignoring environment variable 'SONARQUBE_SCANNER_PARAMS' because 'SONAR_SCANNER_JSON_PARAMS' is set");
}

@Test
void oldJsonEnvVariablesIsIgnoredIfNewIsDefinedButDontLogIfSameValue() {
var inputProperties = new HashMap<String, String>();
EnvironmentConfig.processEnvVariables(inputProperties,
Map.of("SONARQUBE_SCANNER_PARAMS", "{\"key1\":\"value1\", \"key2\":\"value2\"}",
"SONAR_SCANNER_JSON_PARAMS", "{\"key1\":\"value1\", \"key2\":\"value2\"}"));

assertThat(inputProperties).containsOnly(
entry("key1", "value1"),
entry("key2", "value2"));

assertThat(logTester.logs()).isEmpty();
}

}

Notiek ielāde…
Atcelt
Saglabāt