Skip to content

Commit b71c872

Browse files
authored
SOLR-18118: Add in --prompt-inputs to bin/solr start -e cloud to programatically respond to input prompts (#4127)
Adds a non-interactive way to run bin/solr start -e cloud by supplying ordered prompt answers on the command line, enabling scripted/automated cluster startup while keeping existing interactive behavior available.
1 parent e68d292 commit b71c872

File tree

6 files changed

+256
-54
lines changed

6 files changed

+256
-54
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
2+
title: Script creating a cluster using bin/solr start -e cloud with --prompt-inputs option.
3+
type: added # added, changed, fixed, deprecated, removed, dependency_update, security, other
4+
authors:
5+
- name: Eric Pugh
6+
- name: Rahul Goswami
7+
links:
8+
- name: SOLR-18118
9+
url: https://issues.apache.org/jira/browse/SOLR-18118

solr/bin/solr

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -407,12 +407,6 @@ function print_usage() {
407407
echo " --data-home <dir> Sets the solr.data.home system property, where Solr will store index data in <instance_dir>/data subdirectories."
408408
echo " If not set, Solr uses solr.solr.home for config and data."
409409
echo ""
410-
echo " -e/--example <name> Name of the example to run; available examples:"
411-
echo " cloud: SolrCloud example"
412-
echo " techproducts: Comprehensive example illustrating many of Solr's core capabilities"
413-
echo " schemaless: Schema-less example (schema is inferred from data during indexing)"
414-
echo " films: Example of starting with _default configset and adding explicit fields dynamically"
415-
echo ""
416410
echo " --jvm-opts <jvmParams> Additional parameters to pass to the JVM when starting Solr, such as to setup"
417411
echo " Java debug options. For example, to enable a Java debugger to attach to the Solr JVM"
418412
echo " you could pass: --jvm-opts \"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=18983\""
@@ -423,7 +417,15 @@ function print_usage() {
423417
echo " you could pass: -j \"--include-jetty-dir=/etc/jetty/custom/server/\""
424418
echo " In most cases, you should wrap the additional parameters in double quotes."
425419
echo ""
426-
echo " -y/--no-prompt Don't prompt for input; accept all defaults when running examples that accept user input"
420+
echo " -e/--example <name> Name of the example to run; available examples:"
421+
echo " cloud: SolrCloud example"
422+
echo " techproducts: Comprehensive example illustrating many of Solr's core capabilities"
423+
echo " schemaless: Schema-less example (schema is inferred from data during indexing)"
424+
echo " films: Example of starting with _default configset and adding explicit fields dynamically"
425+
echo ""
426+
echo " -y/--no-prompt Don't prompt for input; accept all defaults when running examples that accept user input."
427+
echo ""
428+
echo " --prompt-inputs <values> Don't prompt for input; comma delimited list of inputs read when running examples that accept user input."
427429
echo ""
428430
echo " --force If attempting to start Solr as the root user, the script will exit with a warning that running Solr as \"root\" can cause problems."
429431
echo " It is possible to override this warning with the '--force' parameter."
@@ -817,6 +819,14 @@ if [ $# -gt 0 ]; then
817819
PASS_TO_RUN_EXAMPLE+=("--no-prompt")
818820
shift
819821
;;
822+
--prompt-inputs)
823+
if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
824+
print_usage "$SCRIPT_CMD" "Prompt values are required when using the $1 option!"
825+
exit 1
826+
fi
827+
PASS_TO_RUN_EXAMPLE+=("--prompt-inputs" "$2")
828+
shift 2
829+
;;
820830
--verbose)
821831
verbose=true
822832
SOLR_LOG_LEVEL=DEBUG

solr/bin/solr.cmd

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,6 @@ goto err
325325
@echo --data-home dir Sets the solr.data.home system property, where Solr will store index data in ^<instance_dir^>/data subdirectories.
326326
@echo If not set, Solr uses solr.solr.home for both config and data.
327327
@echo.
328-
@echo -e/--example name Name of the example to run; available examples:
329-
@echo cloud: SolrCloud example
330-
@echo techproducts: Comprehensive example illustrating many of Solr's core capabilities
331-
@echo schemaless: Schema-less example (schema is inferred from data during indexing)
332-
@echo films: Example of starting with _default configset and defining explicit fields dynamically
333-
@echo.
334328
@echo --jvm-opts opts Additional parameters to pass to the JVM when starting Solr, such as to setup
335329
@echo Java debug options. For example, to enable a Java debugger to attach to the Solr JVM
336330
@echo you could pass: --jvm-opts "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=18983"
@@ -341,8 +335,16 @@ goto err
341335
@echo you could pass: -j "--include-jetty-dir=/etc/jetty/custom/server/"
342336
@echo In most cases, you should wrap the additional parameters in double quotes.
343337
@echo.
338+
@echo -e/--example name Name of the example to run; available examples:
339+
@echo cloud: SolrCloud example
340+
@echo techproducts: Comprehensive example illustrating many of Solr's core capabilities
341+
@echo schemaless: Schema-less example (schema is inferred from data during indexing)
342+
@echo films: Example of starting with _default configset and defining explicit fields dynamically
343+
@echo.
344344
@echo -y/--no-prompt Don't prompt for input; accept all defaults when running examples that accept user input
345345
@echo.
346+
@echo --prompt-inputs values Don't prompt for input; comma delimited list of inputs read when running examples that accept user input.
347+
@echo.
346348
@echo --verbose and -q/--quiet Verbose or quiet logging. Sets default log level to DEBUG or WARN instead of INFO
347349
@echo.
348350
goto done
@@ -399,6 +401,7 @@ IF "%1"=="-j" goto set_addl_jetty_config
399401
IF "%1"=="--jettyconfig" goto set_addl_jetty_config
400402
IF "%1"=="-y" goto set_noprompt
401403
IF "%1"=="--no-prompt" goto set_noprompt
404+
IF "%1"=="--prompt-inputs" goto set_prompt_inputs
402405

403406
REM Skip stop arg parsing if not stop command
404407
IF NOT "%SCRIPT_CMD%"=="stop" goto parse_general_args
@@ -695,6 +698,13 @@ set "PASS_TO_RUN_EXAMPLE=--no-prompt !PASS_TO_RUN_EXAMPLE!"
695698
SHIFT
696699
goto parse_args
697700

701+
:set_prompt_inputs
702+
set "PASS_TO_RUN_EXAMPLE=--prompt-inputs %~2 !PASS_TO_RUN_EXAMPLE!"
703+
704+
SHIFT
705+
SHIFT
706+
goto parse_args
707+
698708
REM Handle invalid arguments passed to special commands (start, stop, restart)
699709
:invalid_cmd_line
700710
@echo.

solr/core/src/java/org/apache/solr/cli/RunExampleTool.java

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ public class RunExampleTool extends ToolBase {
7373
"Don't prompt for input; accept all defaults when running examples that accept user input.")
7474
.build();
7575

76+
private static final Option PROMPT_INPUTS_OPTION =
77+
Option.builder()
78+
.longOpt("prompt-inputs")
79+
.hasArg()
80+
.argName("VALUES")
81+
.desc(
82+
"Provide comma-separated values for prompts. Same as --no-prompt but uses provided values instead of defaults. "
83+
+ "Example: --prompt-inputs 3,8983,8984,8985,\"gettingstarted\",2,2,_default")
84+
.build();
85+
7686
private static final Option EXAMPLE_OPTION =
7787
Option.builder("e")
7888
.longOpt("example")
@@ -176,6 +186,7 @@ public class RunExampleTool extends ToolBase {
176186
protected Path exampleDir;
177187
protected Path solrHomeDir;
178188
protected String urlScheme;
189+
private boolean usingPromptInputs = false;
179190

180191
/** Default constructor used by the framework when running as a command-line application. */
181192
public RunExampleTool(ToolRuntime runtime) {
@@ -197,6 +208,7 @@ public String getName() {
197208
public Options getOptions() {
198209
return super.getOptions()
199210
.addOption(NO_PROMPT_OPTION)
211+
.addOption(PROMPT_INPUTS_OPTION)
200212
.addOption(EXAMPLE_OPTION)
201213
.addOption(SCRIPT_OPTION)
202214
.addOption(SERVER_DIR_OPTION)
@@ -214,6 +226,12 @@ public Options getOptions() {
214226

215227
@Override
216228
public void runImpl(CommandLine cli) throws Exception {
229+
if (cli.hasOption(NO_PROMPT_OPTION) && cli.hasOption(PROMPT_INPUTS_OPTION)) {
230+
throw new IllegalArgumentException(
231+
"Cannot use both --no-prompt and --prompt-inputs options together. "
232+
+ "Use --no-prompt to accept defaults, or --prompt-inputs to provide specific values.");
233+
}
234+
217235
this.urlScheme = cli.getOptionValue(URL_SCHEME_OPTION, "http");
218236
String exampleType = cli.getOptionValue(EXAMPLE_OPTION);
219237

@@ -515,6 +533,7 @@ protected void runExample(CommandLine cli, String exampleName) throws Exception
515533

516534
protected void runCloudExample(CommandLine cli) throws Exception {
517535

536+
usingPromptInputs = cli.hasOption(PROMPT_INPUTS_OPTION);
518537
boolean prompt = !cli.hasOption(NO_PROMPT_OPTION);
519538
int numNodes = 2;
520539
int[] cloudPorts = new int[] {8983, 7574, 8984, 7575};
@@ -530,10 +549,24 @@ protected void runCloudExample(CommandLine cli) throws Exception {
530549

531550
echo("\nWelcome to the SolrCloud example!\n");
532551

533-
Scanner readInput = prompt ? new Scanner(userInput, StandardCharsets.UTF_8) : null;
552+
Scanner readInput = null;
553+
if (usingPromptInputs) {
554+
// Create a scanner from the provided prompts
555+
String promptsValue = cli.getOptionValue(PROMPT_INPUTS_OPTION);
556+
InputStream promptsStream =
557+
new java.io.ByteArrayInputStream(promptsValue.getBytes(StandardCharsets.UTF_8));
558+
readInput = new Scanner(promptsStream, StandardCharsets.UTF_8);
559+
readInput.useDelimiter(",");
560+
prompt = true; // Enable prompting code path, but reading from prompts instead of user
561+
} else if (prompt) {
562+
readInput = new Scanner(userInput, StandardCharsets.UTF_8);
563+
}
564+
534565
if (prompt) {
535-
echo(
536-
"This interactive session will help you launch a SolrCloud cluster on your local workstation.");
566+
if (!usingPromptInputs) {
567+
echo(
568+
"This interactive session will help you launch a SolrCloud cluster on your local workstation.");
569+
}
537570

538571
// get the number of nodes to start
539572
numNodes =
@@ -1121,9 +1154,24 @@ protected String prompt(Scanner s, String prompt) {
11211154

11221155
protected String prompt(Scanner s, String prompt, String defaultValue) {
11231156
echo(prompt);
1124-
String nextInput = s.nextLine();
1157+
String nextInput;
1158+
if (usingPromptInputs) {
1159+
// Reading from prompts option - use next() instead of nextLine()
1160+
nextInput = s.hasNext() ? s.next() : null;
1161+
// Echo the value being used from prompts
1162+
if (nextInput != null) {
1163+
echo(nextInput);
1164+
}
1165+
} else {
1166+
// Reading from user input - use nextLine()
1167+
nextInput = s.nextLine();
1168+
}
11251169
if (nextInput != null) {
11261170
nextInput = nextInput.trim();
1171+
// Remove quotes if present (for values like "gettingstarted")
1172+
if (nextInput.startsWith("\"") && nextInput.endsWith("\"")) {
1173+
nextInput = nextInput.substring(1, nextInput.length() - 1);
1174+
}
11271175
if (nextInput.isEmpty()) nextInput = null;
11281176
}
11291177
return (nextInput != null) ? nextInput : defaultValue;

solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,116 @@ public void testInteractiveSolrCloudExample() throws Exception {
525525
executor.execute(org.apache.commons.exec.CommandLine.parse("bin/solr stop -p " + bindPort));
526526
}
527527

528+
/**
529+
* Test the --prompt-inputs option that allows providing all prompt values as a comma-separated
530+
* string without requiring interactive input.
531+
*/
532+
@Test
533+
public void testSolrCloudExampleWithPrompts() throws Exception {
534+
Path solrHomeDir = ExternalPaths.SERVER_HOME;
535+
if (!Files.isDirectory(solrHomeDir))
536+
fail(solrHomeDir + " not found and is required to run this test!");
537+
538+
Path solrExampleDir = createTempDir();
539+
Path solrServerDir = solrHomeDir.getParent();
540+
541+
int bindPort = -1;
542+
try (ServerSocket socket = new ServerSocket(0)) {
543+
bindPort = socket.getLocalPort();
544+
}
545+
546+
String collectionName = "testCloudExampleWithPrompts";
547+
548+
// Provide all prompt values via --prompt-inputs option:
549+
// numNodes, port1, collectionName, numShards, replicationFactor, configName
550+
String promptsValue = "1," + bindPort + ",\"" + collectionName + "\",2,2,_default";
551+
552+
String[] toolArgs =
553+
new String[] {
554+
"--example",
555+
"cloud",
556+
"--server-dir",
557+
solrServerDir.toString(),
558+
"--example-dir",
559+
solrExampleDir.toString(),
560+
"--prompt-inputs",
561+
promptsValue
562+
};
563+
564+
// capture tool output to stdout
565+
CLITestHelper.TestingRuntime runtime = new CLITestHelper.TestingRuntime(true);
566+
567+
RunExampleExecutor executor = new RunExampleExecutor();
568+
closeables.add(executor);
569+
570+
RunExampleTool tool = new RunExampleTool(executor, System.in, runtime);
571+
try {
572+
tool.runTool(SolrCLI.processCommandLineArgs(tool, toolArgs));
573+
} catch (Exception e) {
574+
System.err.println(
575+
"RunExampleTool failed due to: "
576+
+ e
577+
+ "; stdout from tool prior to failure: "
578+
+ runtime.getOutput());
579+
throw e;
580+
}
581+
582+
String toolOutput = runtime.getOutput();
583+
584+
// verify Solr is running on the expected port and verify the collection exists
585+
String solrUrl = "http://localhost:" + bindPort + "/solr";
586+
if (!CLIUtils.safeCheckCollectionExists(solrUrl, collectionName, null)) {
587+
fail(
588+
"After running Solr cloud example with --prompt-inputs, test collection '"
589+
+ collectionName
590+
+ "' not found in Solr at: "
591+
+ solrUrl
592+
+ "; tool output: "
593+
+ toolOutput);
594+
}
595+
596+
// verify the collection was created with the specified parameters
597+
try (CloudSolrClient cloudClient =
598+
new RandomizingCloudSolrClientBuilder(
599+
Collections.singletonList(executor.solrCloudCluster.getZkServer().getZkAddress()),
600+
Optional.empty())
601+
.withDefaultCollection(collectionName)
602+
.build()) {
603+
604+
// index some test docs to verify the collection works
605+
int numDocs = 5;
606+
for (int d = 0; d < numDocs; d++) {
607+
SolrInputDocument doc = new SolrInputDocument();
608+
doc.setField("id", "doc" + d);
609+
doc.setField("test_s", "prompts");
610+
cloudClient.add(doc);
611+
}
612+
cloudClient.commit();
613+
614+
QueryResponse qr = cloudClient.query(new SolrQuery("test_s:prompts"));
615+
assertEquals(
616+
"Expected "
617+
+ numDocs
618+
+ " docs in the "
619+
+ collectionName
620+
+ " collection created via --prompts",
621+
numDocs,
622+
qr.getResults().getNumFound());
623+
}
624+
625+
// Verify output contains the prompts values
626+
assertTrue(
627+
"Tool output should contain the collection name", toolOutput.contains(collectionName));
628+
629+
// delete the collection
630+
DeleteTool deleteTool = new DeleteTool(runtime);
631+
String[] deleteArgs = new String[] {"--name", collectionName, "--solr-url", solrUrl};
632+
deleteTool.runTool(SolrCLI.processCommandLineArgs(deleteTool, deleteArgs));
633+
634+
// stop the test instance
635+
executor.execute(org.apache.commons.exec.CommandLine.parse("bin/solr stop -p " + bindPort));
636+
}
637+
528638
@Test
529639
public void testFailExecuteScript() throws Exception {
530640
Path solrHomeDir = ExternalPaths.SERVER_HOME;

0 commit comments

Comments
 (0)