Skip to content

Commit aafd2f9

Browse files
authored
Move docker env var settings handling out of bash (#85913)
In docker ES allows settings to be set via environment variables. This is currently handled in complex bash logic. This commit moves that logic into EnvironmentAwareCommand where the rest of the initial settings are found. relates #85758
1 parent 580a5bc commit aafd2f9

File tree

6 files changed

+186
-143
lines changed

6 files changed

+186
-143
lines changed

distribution/src/bin/elasticsearch-env

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -92,57 +92,6 @@ ES_PATH_CONF=`cd "$ES_PATH_CONF"; pwd`
9292
9393

9494
if [[ "$ES_DISTRIBUTION_TYPE" == "docker" ]]; then
95-
# Allow environment variables to be set by creating a file with the
96-
# contents, and setting an environment variable with the suffix _FILE to
97-
# point to it. This can be used to provide secrets to a container, without
98-
# the values being specified explicitly when running the container.
99-
source "$ES_HOME/bin/elasticsearch-env-from-file"
100-
101-
# Parse Docker env vars to customize Elasticsearch
102-
#
103-
# e.g. Setting the env var cluster.name=testcluster or ES_CLUSTER_NAME=testcluster
104-
#
105-
# will cause Elasticsearch to be invoked with -Ecluster.name=testcluster
106-
#
107-
# see https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html#_setting_default_settings
108-
109-
declare -a es_arg_array
110-
111-
containsElement () {
112-
local e match="$1"
113-
shift
114-
for e; do [[ "$e" == "$match" ]] && return 0; done
115-
return 1
116-
}
117-
118-
# Elasticsearch settings need to either:
119-
# a. have at least two dot separated lower case words, e.g. `cluster.name`, or
120-
while IFS='=' read -r envvar_key envvar_value; do
121-
es_opt=""
122-
if [[ -n "$envvar_value" ]]; then
123-
es_opt="-E${envvar_key}=${envvar_value}"
124-
fi
125-
if [[ ! -z "${es_opt}" ]] && ! containsElement "${es_opt}" "$@" ; then
126-
es_arg_array+=("${es_opt}")
127-
fi
128-
done <<< "$(env | grep -E '^[-a-z0-9_]+(\.[-a-z0-9_]+)+=')"
129-
130-
# b. be upper cased with underscore separators and prefixed with `ES_SETTING_`, e.g. `ES_SETTING_CLUSTER_NAME`.
131-
# Underscores in setting names are escaped by writing them as a double-underscore e.g. "__"
132-
while IFS='=' read -r envvar_key envvar_value; do
133-
es_opt=""
134-
if [[ -n "$envvar_value" ]]; then
135-
# The long-hand sed `y` command works in any sed variant.
136-
envvar_key="$(echo "$envvar_key" | sed -e 's/^ES_SETTING_//; s/_/./g ; s/\.\./_/g; y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/' )"
137-
es_opt="-E${envvar_key}=${envvar_value}"
138-
fi
139-
if [[ ! -z "${es_opt}" ]] && ! containsElement "${es_opt}" "$@" ; then
140-
es_arg_array+=("${es_opt}")
141-
fi
142-
done <<< "$(env | grep -E '^ES_SETTING(_{1,2}[A-Z]+)+=')"
143-
144-
# Reset the positional parameters to the es_arg_array values and any existing positional params
145-
set -- "$@" "${es_arg_array[@]}"
14695

14796
# The virtual file /proc/self/cgroup should list the current cgroup
14897
# membership. For each hierarchy, you can follow the cgroup path from

libs/cli/src/main/java/org/elasticsearch/cli/Command.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Arrays;
2323
import java.util.Collections;
2424
import java.util.Map;
25+
import java.util.Objects;
2526
import java.util.Properties;
2627
import java.util.stream.Collectors;
2728

@@ -55,8 +56,8 @@ public abstract class Command implements Closeable {
5556
*/
5657
public Command(final String description) {
5758
this.description = description;
58-
this.sysprops = captureSystemProperties();
59-
this.envVars = captureEnvironmentVariables();
59+
this.sysprops = Objects.requireNonNull(captureSystemProperties());
60+
this.envVars = Objects.requireNonNull(captureEnvironmentVariables());
6061
}
6162

6263
private Thread shutdownHookThread;

qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
import static org.elasticsearch.packaging.util.docker.Docker.waitForElasticsearch;
7272
import static org.elasticsearch.packaging.util.docker.DockerFileMatcher.file;
7373
import static org.elasticsearch.packaging.util.docker.DockerRun.builder;
74-
import static org.hamcrest.Matchers.allOf;
7574
import static org.hamcrest.Matchers.arrayContaining;
7675
import static org.hamcrest.Matchers.contains;
7776
import static org.hamcrest.Matchers.containsInAnyOrder;
@@ -754,86 +753,6 @@ public void test085EnvironmentVariablesAreRespectedUnderDockerExec() {
754753
assertThat(result.stdout(), containsString("java.net.UnknownHostException: this.is.not.valid"));
755754
}
756755

757-
/**
758-
* Check that settings are applied when they are supplied as environment variables with names that are:
759-
* <ul>
760-
* <li>Prefixed with {@code ES_SETTING_}</li>
761-
* <li>All uppercase</li>
762-
* <li>Dots (periods) are converted to underscores</li>
763-
* <li>Underscores in setting names are escaped by doubling them</li>
764-
* </ul>
765-
*/
766-
public void test086EnvironmentVariablesInSnakeCaseAreTranslated() {
767-
// Note the double-underscore in the var name here, which retains the underscore in translation
768-
installation = runContainer(distribution(), builder().envVar("ES_SETTING_XPACK_SECURITY_FIPS__MODE_ENABLED", "false"));
769-
770-
final Optional<String> commandLine = sh.run("bash -c 'COLUMNS=2000 ps ax'")
771-
.stdout()
772-
.lines()
773-
.filter(line -> line.contains("org.elasticsearch.bootstrap.Elasticsearch"))
774-
.findFirst();
775-
776-
assertThat(commandLine.isPresent(), equalTo(true));
777-
778-
assertThat(commandLine.get(), containsString("-Expack.security.fips_mode.enabled=false"));
779-
}
780-
781-
/**
782-
* Check that environment variables that do not match the criteria for translation to settings are ignored.
783-
*/
784-
public void test087EnvironmentVariablesInIncorrectFormatAreIgnored() {
785-
installation = runContainer(
786-
distribution(),
787-
builder()
788-
// No ES_SETTING_ prefix
789-
.envVar("XPACK_SECURITY_FIPS__MODE_ENABLED", "false")
790-
// Incomplete prefix
791-
.envVar("ES_XPACK_SECURITY_FIPS__MODE_ENABLED", "false")
792-
// Not underscore-separated
793-
.envVar("ES.SETTING.XPACK.SECURITY.FIPS_MODE.ENABLED", "false")
794-
// Not uppercase
795-
.envVar("es_setting_xpack_security_fips__mode_enabled", "false")
796-
);
797-
798-
final Optional<String> commandLine = sh.run("bash -c 'COLUMNS=2000 ps ax'")
799-
.stdout()
800-
.lines()
801-
.filter(line -> line.contains("org.elasticsearch.bootstrap.Elasticsearch"))
802-
.findFirst();
803-
804-
assertThat(commandLine.isPresent(), equalTo(true));
805-
806-
assertThat(commandLine.get(), not(containsString("-Expack.security.fips_mode.enabled=false")));
807-
}
808-
809-
/**
810-
* Check that settings are applied when they are supplied as environment variables with names that:
811-
* <ul>
812-
* <li>Consist only of lowercase letters, numbers, underscores and hyphens</li>
813-
* <li>Separated by periods</li>
814-
* </ul>
815-
*/
816-
public void test088EnvironmentVariablesInDottedFormatArePassedThrough() {
817-
// Note the double-underscore in the var name here, which retains the underscore in translation
818-
installation = runContainer(
819-
distribution(),
820-
builder().envVar("xpack.security.fips_mode.enabled", "false").envVar("http.cors.allow-methods", "GET")
821-
);
822-
823-
final Optional<String> commandLine = sh.run("bash -c 'COLUMNS=2000 ps ax'")
824-
.stdout()
825-
.lines()
826-
.filter(line -> line.contains("org.elasticsearch.bootstrap.Elasticsearch"))
827-
.findFirst();
828-
829-
assertThat(commandLine.isPresent(), equalTo(true));
830-
831-
assertThat(
832-
commandLine.get(),
833-
allOf(containsString("-Expack.security.fips_mode.enabled=false"), containsString("-Ehttp.cors.allow-methods=GET"))
834-
);
835-
}
836-
837756
/**
838757
* Check whether the elasticsearch-certutil tool has been shipped correctly,
839758
* and if present then it can execute.

qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import java.security.cert.X509Certificate;
4040
import java.util.List;
4141
import java.util.Objects;
42-
import java.util.Optional;
4342
import java.util.concurrent.TimeUnit;
4443
import java.util.stream.Collectors;
4544
import java.util.stream.Stream;
@@ -77,16 +76,14 @@ public static void waitForElasticsearch(Installation installation) throws Except
7776
String configFile = Files.readString(configFilePath, StandardCharsets.UTF_8);
7877
securityEnabled = configFile.contains(SECURITY_DISABLED) == false;
7978
} else {
80-
final Optional<String> commandLine = dockerShell.run("bash -c 'COLUMNS=2000 ps ax'")
79+
securityEnabled = dockerShell.run("env")
8180
.stdout()
8281
.lines()
83-
.filter(line -> line.contains("org.elasticsearch.bootstrap.Elasticsearch"))
84-
.findFirst();
85-
if (commandLine.isPresent() == false) {
86-
throw new RuntimeException("Installation distribution is docker but a docker container is not running");
87-
}
88-
// security is enabled by default, the only way for it to be disabled is to be explicitly disabled
89-
securityEnabled = commandLine.get().contains("-Expack.security.enabled=false") == false;
82+
.filter(each -> each.startsWith("xpack.security.enabled"))
83+
.findFirst()
84+
.map(line -> Boolean.parseBoolean(line.split("=")[1]))
85+
// security is enabled by default, the only way for it to be disabled is to be explicitly disabled
86+
.orElse(true);
9087
}
9188

9289
if (securityEnabled) {

server/src/main/java/org/elasticsearch/common/cli/EnvironmentAwareCommand.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import joptsimple.OptionSpec;
1313
import joptsimple.util.KeyValuePair;
1414

15+
import org.elasticsearch.Build;
1516
import org.elasticsearch.cli.Command;
1617
import org.elasticsearch.cli.ExitCodes;
1718
import org.elasticsearch.cli.Terminal;
@@ -26,10 +27,14 @@
2627
import java.util.HashMap;
2728
import java.util.Locale;
2829
import java.util.Map;
30+
import java.util.regex.Pattern;
2931

3032
/** A cli command which requires an {@link org.elasticsearch.env.Environment} to use current paths and settings. */
3133
public abstract class EnvironmentAwareCommand extends Command {
3234

35+
private static final String DOCKER_UPPERCASE_SETTING_PREFIX = "ES_SETTING_";
36+
private static final Pattern DOCKER_LOWERCASE_SETTING_REGEX = Pattern.compile("[-a-z0-9_]+(\\.[-a-z0-9_]+)+");
37+
3338
private final OptionSpec<KeyValuePair> settingOption;
3439

3540
/**
@@ -48,6 +53,27 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception {
4853
execute(terminal, options, createEnv(options));
4954
}
5055

56+
private void putDockerEnvSettings(Map<String, String> settings, Map<String, String> envVars) {
57+
for (var envVar : envVars.entrySet()) {
58+
String key = envVar.getKey();
59+
if (DOCKER_LOWERCASE_SETTING_REGEX.matcher(key).matches()) {
60+
// all lowercase, like cluster.name, so just put directly
61+
settings.put(key, envVar.getValue());
62+
} else if (key.startsWith(DOCKER_UPPERCASE_SETTING_PREFIX)) {
63+
// remove prefix
64+
key = key.substring(DOCKER_UPPERCASE_SETTING_PREFIX.length());
65+
// insert dots for underscores
66+
key = key.replace('_', '.');
67+
// unescape double dots, which were originally double underscores
68+
key = key.replace("..", "_");
69+
// lowercase the whole thing
70+
key = key.toLowerCase(Locale.ROOT);
71+
72+
settings.put(key, envVar.getValue());
73+
}
74+
}
75+
}
76+
5177
/** Create an {@link Environment} for the command to use. Overrideable for tests. */
5278
protected Environment createEnv(OptionSet options) throws UserException {
5379
final Map<String, String> settings = new HashMap<>();
@@ -68,6 +94,10 @@ protected Environment createEnv(OptionSet options) throws UserException {
6894
settings.put(kvp.key, kvp.value);
6995
}
7096

97+
if (getBuildType() == Build.Type.DOCKER) {
98+
putDockerEnvSettings(settings, envVars);
99+
}
100+
71101
putSystemPropertyIfSettingIsMissing(sysprops, settings, "path.data", "es.path.data");
72102
putSystemPropertyIfSettingIsMissing(sysprops, settings, "path.home", "es.path.home");
73103
putSystemPropertyIfSettingIsMissing(sysprops, settings, "path.logs", "es.path.logs");
@@ -85,6 +115,11 @@ protected Environment createEnv(OptionSet options) throws UserException {
85115
);
86116
}
87117

118+
// protected to allow tests to override
119+
protected Build.Type getBuildType() {
120+
return Build.CURRENT.type();
121+
}
122+
88123
@SuppressForbidden(reason = "need path to construct environment")
89124
private static Path getConfigPath(final String pathConf) {
90125
return Paths.get(pathConf);

0 commit comments

Comments
 (0)