Support `_FILE` suffixed env vars in Docker entrypoint (#49182)
Backport of #47573. Closes #43603. Allow environment variables to be passed to ES in a Docker container via a file, by setting an environment variable with the `_FILE` suffix that points to the file with the intended value of the env var.
This commit is contained in:
parent
b29f4dd9c2
commit
e84e21174b
|
@ -38,6 +38,40 @@ if [[ "$1" != "eswrapper" ]]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
# Allow environment variables to be set by creating a file with the
|
||||
# contents, and setting an environment variable with the suffix _FILE to
|
||||
# point to it. This can be used to provide secrets to a container, without
|
||||
# the values being specified explicitly when running the container.
|
||||
for VAR_NAME_FILE in $(env | cut -f1 -d= | grep '_FILE$'); do
|
||||
if [[ -n "$VAR_NAME_FILE" ]]; then
|
||||
VAR_NAME="${VAR_NAME_FILE%_FILE}"
|
||||
|
||||
if env | grep "^${VAR_NAME}="; then
|
||||
echo "ERROR: Both $VAR_NAME_FILE and $VAR_NAME are set. These are mutually exclusive." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -e "${!VAR_NAME_FILE}" ]]; then
|
||||
echo "ERROR: File ${!VAR_NAME_FILE} from $VAR_NAME_FILE does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILE_PERMS="$(stat -c '%a' ${!VAR_NAME_FILE})"
|
||||
|
||||
if [[ "$FILE_PERMS" != "400" && "$FILE_PERMS" != 600 ]]; then
|
||||
echo "ERROR: File ${!VAR_NAME_FILE} from $VAR_NAME_FILE must have file permissions 400 or 600, but actually has: $FILE_PERMS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Setting $VAR_NAME from $VAR_NAME_FILE at ${!VAR_NAME_FILE}" >&2
|
||||
export "$VAR_NAME"="$(cat ${!VAR_NAME_FILE})"
|
||||
|
||||
unset VAR_NAME
|
||||
# Unset the suffixed environment variable
|
||||
unset "$VAR_NAME_FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
# Parse Docker env vars to customize Elasticsearch
|
||||
#
|
||||
# e.g. Setting the env var cluster.name=testcluster
|
||||
|
|
|
@ -314,6 +314,19 @@ You can set individual {es} configuration parameters using Docker environment va
|
|||
The <<docker-compose-file, sample compose file>> and the
|
||||
<<docker-cli-run-dev-mode, single-node example>> use this method.
|
||||
|
||||
To use the contents of a file to set an environment variable, suffix the environment
|
||||
variable name with `_FILE`. This is useful for passing secrets such as passwords to {es}
|
||||
without specifying them directly.
|
||||
|
||||
For example, to set the {es} bootstrap password from a file, you can bind mount the
|
||||
file and set the `ELASTIC_PASSWORD_FILE` environment variable to the mount location.
|
||||
If you mount the password file to `/run/secrets/password.txt`, specify:
|
||||
|
||||
[source,sh]
|
||||
--------------------------------------------
|
||||
-e ELASTIC_PASSWORD_FILE=/run/secrets/bootstrapPassword.txt
|
||||
--------------------------------------------
|
||||
|
||||
You can also override the default command for the image to pass {es} configuration
|
||||
parameters as command line options. For example:
|
||||
|
||||
|
|
|
@ -25,38 +25,45 @@ import org.elasticsearch.packaging.util.Docker.DockerShell;
|
|||
import org.elasticsearch.packaging.util.Installation;
|
||||
import org.elasticsearch.packaging.util.ServerUtils;
|
||||
import org.elasticsearch.packaging.util.Shell.Result;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.nio.file.attribute.PosixFilePermissions.fromString;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.elasticsearch.packaging.util.Docker.assertPermissionsAndOwnership;
|
||||
import static org.elasticsearch.packaging.util.Docker.copyFromContainer;
|
||||
import static org.elasticsearch.packaging.util.Docker.ensureImageIsLoaded;
|
||||
import static org.elasticsearch.packaging.util.Docker.existsInContainer;
|
||||
import static org.elasticsearch.packaging.util.Docker.removeContainer;
|
||||
import static org.elasticsearch.packaging.util.Docker.runContainer;
|
||||
import static org.elasticsearch.packaging.util.Docker.runContainerExpectingFailure;
|
||||
import static org.elasticsearch.packaging.util.Docker.verifyContainerInstallation;
|
||||
import static org.elasticsearch.packaging.util.Docker.waitForElasticsearch;
|
||||
import static org.elasticsearch.packaging.util.Docker.waitForPathToExist;
|
||||
import static org.elasticsearch.packaging.util.FileMatcher.p600;
|
||||
import static org.elasticsearch.packaging.util.FileMatcher.p660;
|
||||
import static org.elasticsearch.packaging.util.FileUtils.append;
|
||||
import static org.elasticsearch.packaging.util.FileUtils.getTempDir;
|
||||
import static org.elasticsearch.packaging.util.FileUtils.mkdir;
|
||||
import static org.elasticsearch.packaging.util.FileUtils.rm;
|
||||
import static org.elasticsearch.packaging.util.ServerUtils.makeRequest;
|
||||
import static org.elasticsearch.packaging.util.ServerUtils.waitForElasticsearch;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.emptyString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
public class DockerTests extends PackagingTestCase {
|
||||
protected DockerShell sh;
|
||||
private Path tempDir;
|
||||
|
||||
@BeforeClass
|
||||
public static void filterDistros() {
|
||||
|
@ -72,9 +79,15 @@ public class DockerTests extends PackagingTestCase {
|
|||
}
|
||||
|
||||
@Before
|
||||
public void setupTest() throws Exception {
|
||||
public void setupTest() throws IOException {
|
||||
sh = new DockerShell();
|
||||
installation = runContainer(distribution());
|
||||
tempDir = Files.createTempDirectory(getTempDir(), DockerTests.class.getSimpleName());
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardownTest() {
|
||||
rm(tempDir);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,40 +157,152 @@ public class DockerTests extends PackagingTestCase {
|
|||
* Check that the default config can be overridden using a bind mount, and that env vars are respected
|
||||
*/
|
||||
public void test70BindMountCustomPathConfAndJvmOptions() throws Exception {
|
||||
final Path tempConf = getTempDir().resolve("esconf-alternate");
|
||||
|
||||
try {
|
||||
mkdir(tempConf);
|
||||
copyFromContainer(installation.config("elasticsearch.yml"), tempConf.resolve("elasticsearch.yml"));
|
||||
copyFromContainer(installation.config("log4j2.properties"), tempConf.resolve("log4j2.properties"));
|
||||
copyFromContainer(installation.config("elasticsearch.yml"), tempDir.resolve("elasticsearch.yml"));
|
||||
copyFromContainer(installation.config("log4j2.properties"), tempDir.resolve("log4j2.properties"));
|
||||
|
||||
// we have to disable Log4j from using JMX lest it will hit a security
|
||||
// manager exception before we have configured logging; this will fail
|
||||
// startup since we detect usages of logging before it is configured
|
||||
final String jvmOptions =
|
||||
"-Xms512m\n" +
|
||||
"-Xmx512m\n" +
|
||||
"-Dlog4j2.disable.jmx=true\n";
|
||||
append(tempConf.resolve("jvm.options"), jvmOptions);
|
||||
final String jvmOptions = "-Xms512m\n-Xmx512m\n-Dlog4j2.disable.jmx=true\n";
|
||||
append(tempDir.resolve("jvm.options"), jvmOptions);
|
||||
|
||||
// Make the temp directory and contents accessible when bind-mounted
|
||||
Files.setPosixFilePermissions(tempConf, fromString("rwxrwxrwx"));
|
||||
|
||||
final Map<String, String> envVars = new HashMap<>();
|
||||
envVars.put("ES_JAVA_OPTS", "-XX:-UseCompressedOops");
|
||||
Files.setPosixFilePermissions(tempDir, fromString("rwxrwxrwx"));
|
||||
|
||||
// Restart the container
|
||||
removeContainer();
|
||||
runContainer(distribution(), tempConf, envVars);
|
||||
final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/usr/share/elasticsearch/config"));
|
||||
final Map<String, String> envVars = singletonMap("ES_JAVA_OPTS", "-XX:-UseCompressedOops");
|
||||
runContainer(distribution(), volumes, envVars);
|
||||
|
||||
waitForElasticsearch(installation);
|
||||
|
||||
final String nodesResponse = makeRequest(Request.Get("http://localhost:9200/_nodes"));
|
||||
assertThat(nodesResponse, containsString("\"heap_init_in_bytes\":536870912"));
|
||||
assertThat(nodesResponse, containsString("\"using_compressed_ordinary_object_pointers\":\"false\""));
|
||||
} finally {
|
||||
rm(tempConf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that environment variables can be populated by setting variables with the suffix "_FILE",
|
||||
* which point to files that hold the required values.
|
||||
*/
|
||||
public void test80SetEnvironmentVariablesUsingFiles() throws Exception {
|
||||
final String optionsFilename = "esJavaOpts.txt";
|
||||
|
||||
// ES_JAVA_OPTS_FILE
|
||||
append(tempDir.resolve(optionsFilename), "-XX:-UseCompressedOops\n");
|
||||
|
||||
Map<String, String> envVars = singletonMap("ES_JAVA_OPTS_FILE", "/run/secrets/" + optionsFilename);
|
||||
|
||||
// File permissions need to be secured in order for the ES wrapper to accept
|
||||
// them for populating env var values
|
||||
Files.setPosixFilePermissions(tempDir.resolve(optionsFilename), p600);
|
||||
|
||||
final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets"));
|
||||
|
||||
// Restart the container
|
||||
runContainer(distribution(), volumes, envVars);
|
||||
|
||||
waitForElasticsearch(installation);
|
||||
|
||||
final String nodesResponse = makeRequest(Request.Get("http://localhost:9200/_nodes"));
|
||||
|
||||
assertThat(nodesResponse, containsString("\"using_compressed_ordinary_object_pointers\":\"false\""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the elastic user's password can be configured via a file and the ELASTIC_PASSWORD_FILE environment variable.
|
||||
*/
|
||||
public void test81ConfigurePasswordThroughEnvironmentVariableFile() throws Exception {
|
||||
// Test relies on configuring security
|
||||
assumeTrue(distribution.isDefault());
|
||||
|
||||
final String xpackPassword = "hunter2";
|
||||
final String passwordFilename = "password.txt";
|
||||
|
||||
// ELASTIC_PASSWORD_FILE
|
||||
append(tempDir.resolve(passwordFilename), xpackPassword + "\n");
|
||||
|
||||
// Enable security so that we can test that the password has been used
|
||||
Map<String, String> envVars = new HashMap<>();
|
||||
envVars.put("ELASTIC_PASSWORD_FILE", "/run/secrets/" + passwordFilename);
|
||||
envVars.put("xpack.security.enabled", "true");
|
||||
|
||||
// File permissions need to be secured in order for the ES wrapper to accept
|
||||
// them for populating env var values
|
||||
Files.setPosixFilePermissions(tempDir.resolve(passwordFilename), p600);
|
||||
|
||||
final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets"));
|
||||
|
||||
// Restart the container
|
||||
runContainer(distribution(), volumes, envVars);
|
||||
|
||||
// If we configured security correctly, then this call will only work if we specify the correct credentials.
|
||||
try {
|
||||
waitForElasticsearch("green", null, installation, "elastic", "hunter2");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(
|
||||
"Failed to check whether Elasticsearch had started. This could be because "
|
||||
+ "authentication isn't working properly. Check the container logs",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Also check that an unauthenticated call fails
|
||||
final int statusCode = Request.Get("http://localhost:9200/_nodes").execute().returnResponse().getStatusLine().getStatusCode();
|
||||
assertThat("Expected server to require authentication", statusCode, equalTo(401));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that environment variables cannot be used with _FILE environment variables.
|
||||
*/
|
||||
public void test81CannotUseEnvVarsAndFiles() throws Exception {
|
||||
final String optionsFilename = "esJavaOpts.txt";
|
||||
|
||||
// ES_JAVA_OPTS_FILE
|
||||
append(tempDir.resolve(optionsFilename), "-XX:-UseCompressedOops\n");
|
||||
|
||||
Map<String, String> envVars = new HashMap<>();
|
||||
envVars.put("ES_JAVA_OPTS", "-XX:+UseCompressedOops");
|
||||
envVars.put("ES_JAVA_OPTS_FILE", "/run/secrets/" + optionsFilename);
|
||||
|
||||
// File permissions need to be secured in order for the ES wrapper to accept
|
||||
// them for populating env var values
|
||||
Files.setPosixFilePermissions(tempDir.resolve(optionsFilename), p600);
|
||||
|
||||
final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets"));
|
||||
|
||||
final Result dockerLogs = runContainerExpectingFailure(distribution, volumes, envVars);
|
||||
|
||||
assertThat(
|
||||
dockerLogs.stderr,
|
||||
containsString("ERROR: Both ES_JAVA_OPTS_FILE and ES_JAVA_OPTS are set. These are mutually exclusive.")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that when populating environment variables by setting variables with the suffix "_FILE",
|
||||
* the files' permissions are checked.
|
||||
*/
|
||||
public void test82EnvironmentVariablesUsingFilesHaveCorrectPermissions() throws Exception {
|
||||
final String optionsFilename = "esJavaOpts.txt";
|
||||
|
||||
// ES_JAVA_OPTS_FILE
|
||||
append(tempDir.resolve(optionsFilename), "-XX:-UseCompressedOops\n");
|
||||
|
||||
Map<String, String> envVars = singletonMap("ES_JAVA_OPTS_FILE", "/run/secrets/" + optionsFilename);
|
||||
|
||||
// Set invalid file permissions
|
||||
Files.setPosixFilePermissions(tempDir.resolve(optionsFilename), p660);
|
||||
|
||||
final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets"));
|
||||
|
||||
// Restart the container
|
||||
final Result dockerLogs = runContainerExpectingFailure(distribution(), volumes, envVars);
|
||||
|
||||
assertThat(
|
||||
dockerLogs.stderr,
|
||||
containsString("ERROR: File /run/secrets/" + optionsFilename + " from ES_JAVA_OPTS_FILE must have file permissions 400 or 600")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,7 +346,6 @@ public class DockerTests extends PackagingTestCase {
|
|||
final Installation.Executables bin = installation.executables();
|
||||
|
||||
final Result result = sh.run(bin.elasticsearchNode + " -h");
|
||||
assertThat(result.stdout,
|
||||
containsString("A CLI tool to do unsafe cluster and index manipulations on current node"));
|
||||
assertThat(result.stdout, containsString("A CLI tool to do unsafe cluster and index manipulations on current node"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.apache.commons.logging.LogFactory;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -79,8 +78,8 @@ public class Docker {
|
|||
* Runs an Elasticsearch Docker container.
|
||||
* @param distribution details about the docker image being tested.
|
||||
*/
|
||||
public static Installation runContainer(Distribution distribution) throws Exception {
|
||||
return runContainer(distribution, null, Collections.emptyMap());
|
||||
public static Installation runContainer(Distribution distribution) {
|
||||
return runContainer(distribution, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,23 +87,51 @@ public class Docker {
|
|||
* through a bind mount, and passing additional environment variables.
|
||||
*
|
||||
* @param distribution details about the docker image being tested.
|
||||
* @param configPath the path to the config to bind mount, or null
|
||||
* @param envVars environment variables to set when running the container
|
||||
* @param volumes a map that declares any volume mappings to apply, or null
|
||||
* @param envVars environment variables to set when running the container, or null
|
||||
*/
|
||||
public static Installation runContainer(Distribution distribution, Path configPath, Map<String,String> envVars) throws Exception {
|
||||
public static Installation runContainer(Distribution distribution, Map<Path, Path> volumes, Map<String, String> envVars) {
|
||||
executeDockerRun(distribution, volumes, envVars);
|
||||
|
||||
waitForElasticsearchToStart();
|
||||
|
||||
return Installation.ofContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #runContainer(Distribution, Map, Map)} in that it runs an Elasticsearch Docker
|
||||
* container, expect that the container expecting it to exit e.g. due to configuration problem.
|
||||
*
|
||||
* @param distribution details about the docker image being tested.
|
||||
* @param volumes a map that declares any volume mappings to apply, or null
|
||||
* @param envVars environment variables to set when running the container, or null
|
||||
* @return the docker logs of the container
|
||||
*/
|
||||
public static Shell.Result runContainerExpectingFailure(
|
||||
Distribution distribution,
|
||||
Map<Path, Path> volumes,
|
||||
Map<String, String> envVars
|
||||
) {
|
||||
executeDockerRun(distribution, volumes, envVars);
|
||||
|
||||
waitForElasticsearchToExit();
|
||||
|
||||
return sh.run("docker logs " + containerId);
|
||||
}
|
||||
|
||||
private static void executeDockerRun(Distribution distribution, Map<Path, Path> volumes, Map<String, String> envVars) {
|
||||
removeContainer();
|
||||
|
||||
final List<String> args = new ArrayList<>();
|
||||
|
||||
args.add("docker run");
|
||||
|
||||
// Remove the container once it exits
|
||||
args.add("--rm");
|
||||
|
||||
// Run the container in the background
|
||||
args.add("--detach");
|
||||
|
||||
if (envVars != null) {
|
||||
envVars.forEach((key, value) -> args.add("--env " + key + "=\"" + value + "\""));
|
||||
}
|
||||
|
||||
// The container won't run without configuring discovery
|
||||
args.add("--env discovery.type=single-node");
|
||||
|
@ -113,33 +140,30 @@ public class Docker {
|
|||
args.add("--publish 9200:9200");
|
||||
args.add("--publish 9300:9300");
|
||||
|
||||
if (configPath != null) {
|
||||
// Bind-mount the config dir, if specified
|
||||
args.add("--volume \"" + configPath + ":/usr/share/elasticsearch/config\"");
|
||||
// Bind-mount any volumes
|
||||
if (volumes != null) {
|
||||
volumes.forEach((localPath, containerPath) -> args.add("--volume \"" + localPath + ":" + containerPath + "\""));
|
||||
}
|
||||
|
||||
args.add(distribution.flavor.name + ":test");
|
||||
|
||||
final String command = String.join(" ", args);
|
||||
logger.debug("Running command: " + command);
|
||||
logger.info("Running command: " + command);
|
||||
containerId = sh.run(command).stdout.trim();
|
||||
|
||||
waitForElasticsearchToStart();
|
||||
|
||||
return Installation.ofContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the Elasticsearch process to start executing in the container.
|
||||
* This is called every time a container is started.
|
||||
*/
|
||||
private static void waitForElasticsearchToStart() throws InterruptedException {
|
||||
private static void waitForElasticsearchToStart() {
|
||||
boolean isElasticsearchRunning = false;
|
||||
int attempt = 0;
|
||||
|
||||
String psOutput;
|
||||
String psOutput = null;
|
||||
|
||||
do {
|
||||
try {
|
||||
// Give the container a chance to crash out
|
||||
Thread.sleep(1000);
|
||||
|
||||
|
@ -149,12 +173,48 @@ public class Docker {
|
|||
isElasticsearchRunning = true;
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("Caught exception while waiting for ES to start", e);
|
||||
}
|
||||
} while (attempt++ < 5);
|
||||
|
||||
if (!isElasticsearchRunning) {
|
||||
final String dockerLogs = sh.run("docker logs " + containerId).stdout;
|
||||
fail("Elasticsearch container did start successfully.\n\n" + psOutput + "\n\n" + dockerLogs);
|
||||
if (isElasticsearchRunning == false) {
|
||||
final Shell.Result dockerLogs = sh.run("docker logs " + containerId);
|
||||
fail(
|
||||
"Elasticsearch container did not start successfully.\n\nps output:\n"
|
||||
+ psOutput
|
||||
+ "\n\nStdout:\n"
|
||||
+ dockerLogs.stdout
|
||||
+ "\n\nStderr:\n"
|
||||
+ dockerLogs.stderr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the Elasticsearch container to exit.
|
||||
*/
|
||||
private static void waitForElasticsearchToExit() {
|
||||
boolean isElasticsearchRunning = true;
|
||||
int attempt = 0;
|
||||
|
||||
do {
|
||||
try {
|
||||
// Give the container a chance to exit out
|
||||
Thread.sleep(1000);
|
||||
|
||||
if (sh.run("docker ps --quiet --no-trunc").stdout.contains(containerId) == false) {
|
||||
isElasticsearchRunning = false;
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Caught exception while waiting for ES to exit", e);
|
||||
}
|
||||
} while (attempt++ < 5);
|
||||
|
||||
if (isElasticsearchRunning) {
|
||||
final Shell.Result dockerLogs = sh.run("docker logs " + containerId);
|
||||
fail("Elasticsearch container did exit.\n\nStdout:\n" + dockerLogs.stdout + "\n\nStderr:\n" + dockerLogs.stderr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,10 +230,12 @@ public class Docker {
|
|||
final Shell.Result result = sh.runIgnoreExitCode(command);
|
||||
|
||||
if (result.isSuccess() == false) {
|
||||
boolean isErrorAcceptable = result.stderr.contains("removal of container " + containerId + " is already in progress")
|
||||
|| result.stderr.contains("Error: No such container: " + containerId);
|
||||
|
||||
// I'm not sure why we're already removing this container, but that's OK.
|
||||
if (result.stderr.contains("removal of container " + " is already in progress") == false) {
|
||||
throw new RuntimeException(
|
||||
"Command was not successful: [" + command + "] result: " + result.toString());
|
||||
if (isErrorAcceptable == false) {
|
||||
throw new RuntimeException("Command was not successful: [" + command + "] result: " + result.toString());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
@ -204,11 +266,7 @@ public class Docker {
|
|||
protected String[] getScriptCommand(String script) {
|
||||
assert containerId != null;
|
||||
|
||||
return super.getScriptCommand("docker exec " +
|
||||
"--user elasticsearch:root " +
|
||||
"--tty " +
|
||||
containerId + " " +
|
||||
script);
|
||||
return super.getScriptCommand("docker exec " + "--user elasticsearch:root " + "--tty " + containerId + " " + script);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,41 +336,30 @@ public class Docker {
|
|||
final String homeDir = passwdResult.stdout.trim().split(":")[5];
|
||||
assertThat(homeDir, equalTo("/usr/share/elasticsearch"));
|
||||
|
||||
Stream.of(
|
||||
es.home,
|
||||
es.data,
|
||||
es.logs,
|
||||
es.config
|
||||
).forEach(dir -> assertPermissionsAndOwnership(dir, p775));
|
||||
Stream.of(es.home, es.data, es.logs, es.config).forEach(dir -> assertPermissionsAndOwnership(dir, p775));
|
||||
|
||||
Stream.of(
|
||||
es.plugins,
|
||||
es.modules
|
||||
).forEach(dir -> assertPermissionsAndOwnership(dir, p755));
|
||||
Stream.of(es.plugins, es.modules).forEach(dir -> assertPermissionsAndOwnership(dir, p755));
|
||||
|
||||
// FIXME these files should all have the same permissions
|
||||
Stream.of(
|
||||
Stream
|
||||
.of(
|
||||
"elasticsearch.keystore",
|
||||
// "elasticsearch.yml",
|
||||
"jvm.options"
|
||||
// "log4j2.properties"
|
||||
).forEach(configFile -> assertPermissionsAndOwnership(es.config(configFile), p660));
|
||||
)
|
||||
.forEach(configFile -> assertPermissionsAndOwnership(es.config(configFile), p660));
|
||||
|
||||
Stream.of(
|
||||
"elasticsearch.yml",
|
||||
"log4j2.properties"
|
||||
).forEach(configFile -> assertPermissionsAndOwnership(es.config(configFile), p644));
|
||||
Stream
|
||||
.of("elasticsearch.yml", "log4j2.properties")
|
||||
.forEach(configFile -> assertPermissionsAndOwnership(es.config(configFile), p644));
|
||||
|
||||
assertThat(
|
||||
dockerShell.run(es.bin("elasticsearch-keystore") + " list").stdout,
|
||||
containsString("keystore.seed"));
|
||||
assertThat(dockerShell.run(es.bin("elasticsearch-keystore") + " list").stdout, containsString("keystore.seed"));
|
||||
|
||||
Stream.of(
|
||||
es.bin,
|
||||
es.lib
|
||||
).forEach(dir -> assertPermissionsAndOwnership(dir, p755));
|
||||
Stream.of(es.bin, es.lib).forEach(dir -> assertPermissionsAndOwnership(dir, p755));
|
||||
|
||||
Stream.of(
|
||||
Stream
|
||||
.of(
|
||||
"elasticsearch",
|
||||
"elasticsearch-cli",
|
||||
"elasticsearch-env",
|
||||
|
@ -321,17 +368,15 @@ public class Docker {
|
|||
"elasticsearch-node",
|
||||
"elasticsearch-plugin",
|
||||
"elasticsearch-shard"
|
||||
).forEach(executable -> assertPermissionsAndOwnership(es.bin(executable), p755));
|
||||
)
|
||||
.forEach(executable -> assertPermissionsAndOwnership(es.bin(executable), p755));
|
||||
|
||||
Stream.of(
|
||||
"LICENSE.txt",
|
||||
"NOTICE.txt",
|
||||
"README.textile"
|
||||
).forEach(doc -> assertPermissionsAndOwnership(es.home.resolve(doc), p644));
|
||||
Stream.of("LICENSE.txt", "NOTICE.txt", "README.textile").forEach(doc -> assertPermissionsAndOwnership(es.home.resolve(doc), p644));
|
||||
}
|
||||
|
||||
private static void verifyDefaultInstallation(Installation es) {
|
||||
Stream.of(
|
||||
Stream
|
||||
.of(
|
||||
"elasticsearch-certgen",
|
||||
"elasticsearch-certutil",
|
||||
"elasticsearch-croneval",
|
||||
|
@ -343,17 +388,38 @@ public class Docker {
|
|||
"x-pack-env",
|
||||
"x-pack-security-env",
|
||||
"x-pack-watcher-env"
|
||||
).forEach(executable -> assertPermissionsAndOwnership(es.bin(executable), p755));
|
||||
)
|
||||
.forEach(executable -> assertPermissionsAndOwnership(es.bin(executable), p755));
|
||||
|
||||
// at this time we only install the current version of archive distributions, but if that changes we'll need to pass
|
||||
// the version through here
|
||||
assertPermissionsAndOwnership(es.bin("elasticsearch-sql-cli-" + getCurrentVersion() + ".jar"), p755);
|
||||
|
||||
Stream.of(
|
||||
"role_mapping.yml",
|
||||
"roles.yml",
|
||||
"users",
|
||||
"users_roles"
|
||||
).forEach(configFile -> assertPermissionsAndOwnership(es.config(configFile), p660));
|
||||
Stream
|
||||
.of("role_mapping.yml", "roles.yml", "users", "users_roles")
|
||||
.forEach(configFile -> assertPermissionsAndOwnership(es.config(configFile), p660));
|
||||
}
|
||||
|
||||
public static void waitForElasticsearch(Installation installation) throws Exception {
|
||||
withLogging(() -> ServerUtils.waitForElasticsearch(installation));
|
||||
}
|
||||
|
||||
public static void waitForElasticsearch(String status, String index, Installation installation, String username, String password)
|
||||
throws Exception {
|
||||
withLogging(() -> ServerUtils.waitForElasticsearch(status, index, installation, username, password));
|
||||
}
|
||||
|
||||
private static void withLogging(ThrowingRunnable r) throws Exception {
|
||||
try {
|
||||
r.run();
|
||||
} catch (Exception e) {
|
||||
final Shell.Result logs = sh.run("docker logs " + containerId);
|
||||
logger.warn("Elasticsearch container failed to start.\n\nStdout:\n" + logs.stdout + "\n\nStderr:\n" + logs.stderr);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private interface ThrowingRunnable {
|
||||
void run() throws Exception;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public class FileMatcher extends TypeSafeMatcher<Path> {
|
|||
public static final Set<PosixFilePermission> p750 = fromString("rwxr-x---");
|
||||
public static final Set<PosixFilePermission> p660 = fromString("rw-rw----");
|
||||
public static final Set<PosixFilePermission> p644 = fromString("rw-r--r--");
|
||||
public static final Set<PosixFilePermission> p600 = fromString("rw-------");
|
||||
|
||||
private final Fileness fileness;
|
||||
private final String owner;
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
package org.elasticsearch.packaging.util;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.fluent.Executor;
|
||||
import org.apache.http.client.fluent.Request;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
@ -35,7 +37,7 @@ import static org.hamcrest.Matchers.containsString;
|
|||
|
||||
public class ServerUtils {
|
||||
|
||||
protected static final Logger logger = LogManager.getLogger(ServerUtils.class);
|
||||
private static final Logger logger = LogManager.getLogger(ServerUtils.class);
|
||||
|
||||
// generous timeout as nested virtualization can be quite slow ...
|
||||
private static final long waitTime = TimeUnit.MINUTES.toMillis(3);
|
||||
|
@ -43,10 +45,36 @@ public class ServerUtils {
|
|||
private static final long requestInterval = TimeUnit.SECONDS.toMillis(5);
|
||||
|
||||
public static void waitForElasticsearch(Installation installation) throws IOException {
|
||||
waitForElasticsearch("green", null, installation);
|
||||
waitForElasticsearch("green", null, installation, null, null);
|
||||
}
|
||||
|
||||
public static void waitForElasticsearch(String status, String index, Installation installation) throws IOException {
|
||||
/**
|
||||
* Executes the supplied request, optionally applying HTTP basic auth if the
|
||||
* username and pasword field are supplied.
|
||||
* @param request the request to execute
|
||||
* @param username the username to supply, or null
|
||||
* @param password the password to supply, or null
|
||||
* @return the response from the server
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
private static HttpResponse execute(Request request, String username, String password) throws IOException {
|
||||
final Executor executor = Executor.newInstance();
|
||||
|
||||
if (username != null && password != null) {
|
||||
executor.auth(username, password);
|
||||
executor.authPreemptive(new HttpHost("localhost", 9200));
|
||||
}
|
||||
|
||||
return executor.execute(request).returnResponse();
|
||||
}
|
||||
|
||||
public static void waitForElasticsearch(
|
||||
String status,
|
||||
String index,
|
||||
Installation installation,
|
||||
String username,
|
||||
String password
|
||||
) throws IOException {
|
||||
|
||||
Objects.requireNonNull(status);
|
||||
|
||||
|
@ -56,15 +84,19 @@ public class ServerUtils {
|
|||
long timeElapsed = 0;
|
||||
boolean started = false;
|
||||
Throwable thrownException = null;
|
||||
|
||||
while (started == false && timeElapsed < waitTime) {
|
||||
if (System.currentTimeMillis() - lastRequest > requestInterval) {
|
||||
try {
|
||||
|
||||
final HttpResponse response = Request.Get("http://localhost:9200/_cluster/health")
|
||||
final HttpResponse response = execute(
|
||||
Request
|
||||
.Get("http://localhost:9200/_cluster/health")
|
||||
.connectTimeout((int) timeoutLength)
|
||||
.socketTimeout((int) timeoutLength)
|
||||
.execute()
|
||||
.returnResponse();
|
||||
.socketTimeout((int) timeoutLength),
|
||||
username,
|
||||
password
|
||||
);
|
||||
|
||||
if (response.getStatusLine().getStatusCode() >= 300) {
|
||||
final String statusLine = response.getStatusLine().toString();
|
||||
|
@ -101,10 +133,9 @@ public class ServerUtils {
|
|||
url = "http://localhost:9200/_cluster/health?wait_for_status=" + status + "&timeout=60s&pretty";
|
||||
} else {
|
||||
url = "http://localhost:9200/_cluster/health/" + index + "?wait_for_status=" + status + "&timeout=60s&pretty";
|
||||
|
||||
}
|
||||
|
||||
final String body = makeRequest(Request.Get(url));
|
||||
final String body = makeRequest(Request.Get(url), username, password);
|
||||
assertThat("cluster health response must contain desired status", body, containsString(status));
|
||||
}
|
||||
|
||||
|
@ -124,7 +155,11 @@ public class ServerUtils {
|
|||
}
|
||||
|
||||
public static String makeRequest(Request request) throws IOException {
|
||||
final HttpResponse response = request.execute().returnResponse();
|
||||
return makeRequest(request, null, null);
|
||||
}
|
||||
|
||||
public static String makeRequest(Request request, String username, String password) throws IOException {
|
||||
final HttpResponse response = execute(request, username, password);
|
||||
final String body = EntityUtils.toString(response.getEntity());
|
||||
|
||||
if (response.getStatusLine().getStatusCode() >= 300) {
|
||||
|
@ -132,6 +167,5 @@ public class ServerUtils {
|
|||
}
|
||||
|
||||
return body;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue