Skip to content

Commit 6af89e6

Browse files
committed
Allow keystore add-file to handle multiple settings (#54240)
Today the keystore add-file command can only handle adding a single setting/file pair in a single invocation. This incurs the startup costs of the JVM many times, which in some environments can be expensive. This commit teaches the add-file keystore command to accept adding multiple settings in a single invocation.
1 parent 4fbc0e9 commit 6af89e6

File tree

3 files changed

+69
-36
lines changed

3 files changed

+69
-36
lines changed

docs/reference/commands/keystore.asciidoc

+13-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ in the {es} keystore.
1212
--------------------------------------------------
1313
bin/elasticsearch-keystore
1414
([add <settings>] [-f] [--stdin] |
15-
[add-file <setting> <path>] | [create] [-p] |
15+
[add-file (<setting> <path>)+] | [create] [-p] |
1616
[list] | [passwd] | [remove <setting>] | [upgrade])
1717
[-h, --help] ([-s, --silent] | [-v, --verbose])
1818
--------------------------------------------------
@@ -48,7 +48,7 @@ must confirm that you want to overwrite the current value. If the keystore does
4848
not exist, you must confirm that you want to create a keystore. To avoid these
4949
two confirmation prompts, use the `-f` parameter.
5050

51-
`add-file <setting> <path>`:: Adds a file to the keystore.
51+
`add-file (<setting> <path>)+`:: Adds files to the keystore.
5252

5353
`create`:: Creates the keystore.
5454

@@ -173,14 +173,23 @@ Values for multiple settings must be separated by carriage returns or newlines.
173173
==== Add files to the keystore
174174

175175
You can add sensitive files, like authentication key files for Cloud plugins,
176-
using the `add-file` command. Be sure to include your file path as an argument
177-
after the setting name.
176+
using the `add-file` command. Settings and file paths are specified in pairs
177+
consisting of `setting path`.
178178

179179
[source,sh]
180180
----------------------------------------------------------------
181181
bin/elasticsearch-keystore add-file the.setting.name.to.set /path/example-file.json
182182
----------------------------------------------------------------
183183

184+
You can add multiple files with the `add-file` command:
185+
186+
[source,sh]
187+
----------------------------------------------------------------
188+
bin/elasticsearch-keystore add-file \
189+
the.setting.name.to.set /path/example-file.json \
190+
the.other.setting.name.to.set /path/other-example-file.json
191+
----------------------------------------------------------------
192+
184193
If the {es} keystore is password protected, you are prompted to enter the
185194
password.
186195

server/src/main/java/org/elasticsearch/common/settings/AddFileKeyStoreCommand.java

+28-24
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@
1919

2020
package org.elasticsearch.common.settings;
2121

22-
import java.nio.file.Files;
23-
import java.nio.file.Path;
24-
import java.util.Arrays;
25-
import java.util.List;
26-
2722
import joptsimple.OptionSet;
2823
import joptsimple.OptionSpec;
2924
import org.elasticsearch.cli.ExitCodes;
@@ -33,6 +28,11 @@
3328
import org.elasticsearch.common.io.PathUtils;
3429
import org.elasticsearch.env.Environment;
3530

31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
33+
import java.util.Arrays;
34+
import java.util.List;
35+
3636
/**
3737
* A subcommand for the keystore cli which adds a file setting.
3838
*/
@@ -47,41 +47,45 @@ class AddFileKeyStoreCommand extends BaseKeyStoreCommand {
4747
// jopt simple has issue with multiple non options, so we just get one set of them here
4848
// and convert to File when necessary
4949
// see https://github.com/jopt-simple/jopt-simple/issues/103
50-
this.arguments = parser.nonOptions("setting [filepath]");
50+
this.arguments = parser.nonOptions("(setting path)+");
5151
}
5252

5353
@Override
5454
protected void executeCommand(Terminal terminal, OptionSet options, Environment env) throws Exception {
55-
List<String> argumentValues = arguments.values(options);
55+
final List<String> argumentValues = arguments.values(options);
5656
if (argumentValues.size() == 0) {
5757
throw new UserException(ExitCodes.USAGE, "Missing setting name");
5858
}
59-
String setting = argumentValues.get(0);
59+
if (argumentValues.size() % 2 != 0) {
60+
throw new UserException(ExitCodes.USAGE, "settings and filenames must come in pairs");
61+
}
62+
6063
final KeyStoreWrapper keyStore = getKeyStore();
61-
if (keyStore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
62-
if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
63-
terminal.println("Exiting without modifying keystore.");
64-
return;
64+
65+
for (int i = 0; i < argumentValues.size(); i += 2) {
66+
final String setting = argumentValues.get(i);
67+
68+
if (keyStore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
69+
if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
70+
terminal.println("Exiting without modifying keystore.");
71+
return;
72+
}
6573
}
66-
}
6774

68-
if (argumentValues.size() == 1) {
69-
throw new UserException(ExitCodes.USAGE, "Missing file name");
70-
}
71-
Path file = getPath(argumentValues.get(1));
72-
if (Files.exists(file) == false) {
73-
throw new UserException(ExitCodes.IO_ERROR, "File [" + file.toString() + "] does not exist");
74-
}
75-
if (argumentValues.size() > 2) {
76-
throw new UserException(ExitCodes.USAGE, "Unrecognized extra arguments [" +
77-
String.join(", ", argumentValues.subList(2, argumentValues.size())) + "] after filepath");
75+
final Path file = getPath(argumentValues.get(i + 1));
76+
if (Files.exists(file) == false) {
77+
throw new UserException(ExitCodes.IO_ERROR, "File [" + file.toString() + "] does not exist");
78+
}
79+
80+
keyStore.setFile(setting, Files.readAllBytes(file));
7881
}
79-
keyStore.setFile(setting, Files.readAllBytes(file));
82+
8083
keyStore.save(env.configFile(), getKeyStorePassword().getChars());
8184
}
8285

8386
@SuppressForbidden(reason = "file arg for cli")
8487
private Path getPath(String file) {
8588
return PathUtils.get(file);
8689
}
90+
8791
}

server/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java

+28-8
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@
1919

2020
package org.elasticsearch.common.settings;
2121

22-
import java.io.IOException;
23-
import java.nio.file.Files;
24-
import java.nio.file.Path;
25-
import java.util.Map;
26-
2722
import org.elasticsearch.cli.Command;
2823
import org.elasticsearch.cli.ExitCodes;
2924
import org.elasticsearch.cli.UserException;
25+
import org.elasticsearch.common.collect.Tuple;
3026
import org.elasticsearch.env.Environment;
3127

28+
import java.io.IOException;
29+
import java.nio.file.Files;
30+
import java.nio.file.Path;
31+
import java.util.ArrayList;
32+
import java.util.List;
33+
import java.util.Map;
34+
import java.util.stream.Stream;
35+
3236
import static org.hamcrest.Matchers.anyOf;
3337
import static org.hamcrest.Matchers.containsString;
3438

@@ -49,7 +53,7 @@ private Path createRandomFile() throws IOException {
4953
for (int i = 0; i < length; ++i) {
5054
bytes[i] = randomByte();
5155
}
52-
Path file = env.configFile().resolve("randomfile");
56+
Path file = env.configFile().resolve(randomAlphaOfLength(16));
5357
Files.write(file, bytes);
5458
return file;
5559
}
@@ -164,7 +168,7 @@ public void testMissingFileName() throws Exception {
164168
terminal.addSecretInput(password);
165169
UserException e = expectThrows(UserException.class, () -> execute("foo"));
166170
assertEquals(ExitCodes.USAGE, e.exitCode);
167-
assertThat(e.getMessage(), containsString("Missing file name"));
171+
assertThat(e.getMessage(), containsString("settings and filenames must come in pairs"));
168172
}
169173

170174
public void testFileDNE() throws Exception {
@@ -183,7 +187,7 @@ public void testExtraArguments() throws Exception {
183187
terminal.addSecretInput(password);
184188
UserException e = expectThrows(UserException.class, () -> execute("foo", file.toString(), "bar"));
185189
assertEquals(e.getMessage(), ExitCodes.USAGE, e.exitCode);
186-
assertThat(e.getMessage(), containsString("Unrecognized extra arguments [bar]"));
190+
assertThat(e.getMessage(), containsString("settings and filenames must come in pairs"));
187191
}
188192

189193
public void testIncorrectPassword() throws Exception {
@@ -216,4 +220,20 @@ public void testAddToUnprotectedKeystore() throws Exception {
216220
execute("foo", "path/dne");
217221
assertSecureFile("foo", file, password);
218222
}
223+
224+
public void testAddMultipleFiles() throws Exception {
225+
final String password = "keystorepassword";
226+
createKeystore(password);
227+
final int n = randomIntBetween(1, 8);
228+
final List<Tuple<String, Path>> settingFilePairs = new ArrayList<>(n);
229+
for (int i = 0; i < n; i++) {
230+
settingFilePairs.add(Tuple.tuple("foo" + i, createRandomFile()));
231+
}
232+
terminal.addSecretInput(password);
233+
execute(settingFilePairs.stream().flatMap(t -> Stream.of(t.v1(), t.v2().toString())).toArray(String[]::new));
234+
for (int i = 0; i < n; i++) {
235+
assertSecureFile(settingFilePairs.get(i).v1(), settingFilePairs.get(i).v2(), password);
236+
}
237+
}
238+
219239
}

0 commit comments

Comments
 (0)