Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog/unreleased/SOLR-18118.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
title: Script creating a cluster using bin/solr start -e cloud with --prompt-inputs option.
type: added # added, changed, fixed, deprecated, removed, dependency_update, security, other
authors:
- name: Eric Pugh
- name: Rahul Goswami
links:
- name: SOLR-18118
url: https://issues.apache.org/jira/browse/SOLR-18118
24 changes: 17 additions & 7 deletions solr/bin/solr
Original file line number Diff line number Diff line change
Expand Up @@ -407,12 +407,6 @@ function print_usage() {
echo " --data-home <dir> Sets the solr.data.home system property, where Solr will store index data in <instance_dir>/data subdirectories."
echo " If not set, Solr uses solr.solr.home for config and data."
echo ""
echo " -e/--example <name> Name of the example to run; available examples:"
echo " cloud: SolrCloud example"
echo " techproducts: Comprehensive example illustrating many of Solr's core capabilities"
echo " schemaless: Schema-less example (schema is inferred from data during indexing)"
echo " films: Example of starting with _default configset and adding explicit fields dynamically"
echo ""
echo " --jvm-opts <jvmParams> Additional parameters to pass to the JVM when starting Solr, such as to setup"
echo " Java debug options. For example, to enable a Java debugger to attach to the Solr JVM"
echo " you could pass: --jvm-opts \"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=18983\""
Expand All @@ -423,7 +417,15 @@ function print_usage() {
echo " you could pass: -j \"--include-jetty-dir=/etc/jetty/custom/server/\""
echo " In most cases, you should wrap the additional parameters in double quotes."
echo ""
echo " -y/--no-prompt Don't prompt for input; accept all defaults when running examples that accept user input"
echo " -e/--example <name> Name of the example to run; available examples:"
echo " cloud: SolrCloud example"
echo " techproducts: Comprehensive example illustrating many of Solr's core capabilities"
echo " schemaless: Schema-less example (schema is inferred from data during indexing)"
echo " films: Example of starting with _default configset and adding explicit fields dynamically"
echo ""
echo " -y/--no-prompt Don't prompt for input; accept all defaults when running examples that accept user input."
echo ""
echo " --prompt-inputs <values> Don't prompt for input; comma delimited list of inputs read when running examples that accept user input."
echo ""
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."
echo " It is possible to override this warning with the '--force' parameter."
Expand Down Expand Up @@ -817,6 +819,14 @@ if [ $# -gt 0 ]; then
PASS_TO_RUN_EXAMPLE+=("--no-prompt")
shift
;;
--prompt-inputs)
if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
print_usage "$SCRIPT_CMD" "Prompt values are required when using the $1 option!"
exit 1
fi
PASS_TO_RUN_EXAMPLE+=("--prompt-inputs" "$2")
shift 2
;;
--verbose)
verbose=true
SOLR_LOG_LEVEL=DEBUG
Expand Down
22 changes: 16 additions & 6 deletions solr/bin/solr.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,6 @@ goto err
@echo --data-home dir Sets the solr.data.home system property, where Solr will store index data in ^<instance_dir^>/data subdirectories.
@echo If not set, Solr uses solr.solr.home for both config and data.
@echo.
@echo -e/--example name Name of the example to run; available examples:
@echo cloud: SolrCloud example
@echo techproducts: Comprehensive example illustrating many of Solr's core capabilities
@echo schemaless: Schema-less example (schema is inferred from data during indexing)
@echo films: Example of starting with _default configset and defining explicit fields dynamically
@echo.
@echo --jvm-opts opts Additional parameters to pass to the JVM when starting Solr, such as to setup
@echo Java debug options. For example, to enable a Java debugger to attach to the Solr JVM
@echo you could pass: --jvm-opts "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=18983"
Expand All @@ -341,8 +335,16 @@ goto err
@echo you could pass: -j "--include-jetty-dir=/etc/jetty/custom/server/"
@echo In most cases, you should wrap the additional parameters in double quotes.
@echo.
@echo -e/--example name Name of the example to run; available examples:
@echo cloud: SolrCloud example
@echo techproducts: Comprehensive example illustrating many of Solr's core capabilities
@echo schemaless: Schema-less example (schema is inferred from data during indexing)
@echo films: Example of starting with _default configset and defining explicit fields dynamically
@echo.
@echo -y/--no-prompt Don't prompt for input; accept all defaults when running examples that accept user input
@echo.
@echo --prompt-inputs values Don't prompt for input; comma delimited list of inputs read when running examples that accept user input.
@echo.
@echo --verbose and -q/--quiet Verbose or quiet logging. Sets default log level to DEBUG or WARN instead of INFO
@echo.
goto done
Expand Down Expand Up @@ -399,6 +401,7 @@ IF "%1"=="-j" goto set_addl_jetty_config
IF "%1"=="--jettyconfig" goto set_addl_jetty_config
IF "%1"=="-y" goto set_noprompt
IF "%1"=="--no-prompt" goto set_noprompt
IF "%1"=="--prompt-inputs" goto set_prompt_inputs

REM Skip stop arg parsing if not stop command
IF NOT "%SCRIPT_CMD%"=="stop" goto parse_general_args
Expand Down Expand Up @@ -695,6 +698,13 @@ set "PASS_TO_RUN_EXAMPLE=--no-prompt !PASS_TO_RUN_EXAMPLE!"
SHIFT
goto parse_args

:set_prompt_inputs
set "PASS_TO_RUN_EXAMPLE=--prompt-inputs %~2 !PASS_TO_RUN_EXAMPLE!"

SHIFT
SHIFT
goto parse_args

REM Handle invalid arguments passed to special commands (start, stop, restart)
:invalid_cmd_line
@echo.
Expand Down
56 changes: 52 additions & 4 deletions solr/core/src/java/org/apache/solr/cli/RunExampleTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ public class RunExampleTool extends ToolBase {
"Don't prompt for input; accept all defaults when running examples that accept user input.")
.build();

private static final Option PROMPT_INPUTS_OPTION =
Option.builder()
.longOpt("prompt-inputs")
.hasArg()
.argName("VALUES")
.desc(
"Provide comma-separated values for prompts. Same as --no-prompt but uses provided values instead of defaults. "
+ "Example: --prompt-inputs 3,8983,8984,8985,\"gettingstarted\",2,2,_default")
.build();

private static final Option EXAMPLE_OPTION =
Option.builder("e")
.longOpt("example")
Expand Down Expand Up @@ -176,6 +186,7 @@ public class RunExampleTool extends ToolBase {
protected Path exampleDir;
protected Path solrHomeDir;
protected String urlScheme;
private boolean usingPromptInputs = false;

/** Default constructor used by the framework when running as a command-line application. */
public RunExampleTool(ToolRuntime runtime) {
Expand All @@ -197,6 +208,7 @@ public String getName() {
public Options getOptions() {
return super.getOptions()
.addOption(NO_PROMPT_OPTION)
.addOption(PROMPT_INPUTS_OPTION)
.addOption(EXAMPLE_OPTION)
.addOption(SCRIPT_OPTION)
.addOption(SERVER_DIR_OPTION)
Expand All @@ -214,6 +226,12 @@ public Options getOptions() {

@Override
public void runImpl(CommandLine cli) throws Exception {
if (cli.hasOption(NO_PROMPT_OPTION) && cli.hasOption(PROMPT_INPUTS_OPTION)) {
throw new IllegalArgumentException(
"Cannot use both --no-prompt and --prompt-inputs options together. "
+ "Use --no-prompt to accept defaults, or --prompt-inputs to provide specific values.");
}

this.urlScheme = cli.getOptionValue(URL_SCHEME_OPTION, "http");
String exampleType = cli.getOptionValue(EXAMPLE_OPTION);

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

protected void runCloudExample(CommandLine cli) throws Exception {

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

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

Scanner readInput = prompt ? new Scanner(userInput, StandardCharsets.UTF_8) : null;
Scanner readInput = null;
if (usingPromptInputs) {
// Create a scanner from the provided prompts
String promptsValue = cli.getOptionValue(PROMPT_INPUTS_OPTION);
InputStream promptsStream =
new java.io.ByteArrayInputStream(promptsValue.getBytes(StandardCharsets.UTF_8));
Comment on lines +556 to +557
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Scanner with useDelimiter(",") will skip empty tokens (e.g. a,,b), so users can’t "accept default" for a prompt in the middle while still supplying later values. Consider parsing with a CSV-aware parser or split(",", -1) to preserve empty fields so prompt positions stay aligned.

Suggested change
InputStream promptsStream =
new java.io.ByteArrayInputStream(promptsValue.getBytes(StandardCharsets.UTF_8));
// Preserve empty fields so that users can "accept default" for a prompt
// while still providing values for later prompts.
String[] promptTokens = promptsValue.split(",", -1);
for (int i = 0; i < promptTokens.length; i++) {
if (promptTokens[i].isEmpty()) {
// Use a single space so that Scanner produces a token, which the
// prompting logic can then trim() back to an empty string.
promptTokens[i] = " ";
}
}
String processedPromptsValue = String.join(",", promptTokens);
InputStream promptsStream =
new java.io.ByteArrayInputStream(
processedPromptsValue.getBytes(StandardCharsets.UTF_8));

Copilot uses AI. Check for mistakes.
readInput = new Scanner(promptsStream, StandardCharsets.UTF_8);
readInput.useDelimiter(",");
prompt = true; // Enable prompting code path, but reading from prompts instead of user
} else if (prompt) {
readInput = new Scanner(userInput, StandardCharsets.UTF_8);
}

if (prompt) {
echo(
"This interactive session will help you launch a SolrCloud cluster on your local workstation.");
if (!usingPromptInputs) {
echo(
"This interactive session will help you launch a SolrCloud cluster on your local workstation.");
}

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

protected String prompt(Scanner s, String prompt, String defaultValue) {
echo(prompt);
String nextInput = s.nextLine();
String nextInput;
if (usingPromptInputs) {
// Reading from prompts option - use next() instead of nextLine()
nextInput = s.hasNext() ? s.next() : null;
// Echo the value being used from prompts
if (nextInput != null) {
echo(nextInput);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to print here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried with and without and it looks much better with! Plus you can debug it. I was hoping that https://clig.dev/#interactivity would give some specific advice on formatting, but it didn't hold any wisdom...

}
} else {
// Reading from user input - use nextLine()
Copy link
Contributor

@rahulgoswami rahulgoswami Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest moving echo(prompt); from the first line of the method to over here. It would be fair to expect the CLI output for --prompt-inputs to be similar to that of --no-prompt, aka without lines asking for inputs, since we are already providing them in one shot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like seeing the lines because it helps me with debugging... I did defaultss instead of default for a configset name and this let me see the error.

nextInput = s.nextLine();
}
if (nextInput != null) {
nextInput = nextInput.trim();
// Remove quotes if present (for values like "gettingstarted")
if (nextInput.startsWith("\"") && nextInput.endsWith("\"")) {
nextInput = nextInput.substring(1, nextInput.length() - 1);
}
if (nextInput.isEmpty()) nextInput = null;
}
return (nextInput != null) ? nextInput : defaultValue;
Expand Down
110 changes: 110 additions & 0 deletions solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,116 @@ public void testInteractiveSolrCloudExample() throws Exception {
executor.execute(org.apache.commons.exec.CommandLine.parse("bin/solr stop -p " + bindPort));
}

/**
* Test the --prompt-inputs option that allows providing all prompt values as a comma-separated
* string without requiring interactive input.
*/
@Test
public void testSolrCloudExampleWithPrompts() throws Exception {
Path solrHomeDir = ExternalPaths.SERVER_HOME;
if (!Files.isDirectory(solrHomeDir))
fail(solrHomeDir + " not found and is required to run this test!");

Path solrExampleDir = createTempDir();
Path solrServerDir = solrHomeDir.getParent();

int bindPort = -1;
try (ServerSocket socket = new ServerSocket(0)) {
bindPort = socket.getLocalPort();
}

String collectionName = "testCloudExampleWithPrompts";

// Provide all prompt values via --prompt-inputs option:
// numNodes, port1, collectionName, numShards, replicationFactor, configName
String promptsValue = "1," + bindPort + ",\"" + collectionName + "\",2,2,_default";

String[] toolArgs =
new String[] {
"--example",
"cloud",
"--server-dir",
solrServerDir.toString(),
"--example-dir",
solrExampleDir.toString(),
"--prompt-inputs",
promptsValue
};

// capture tool output to stdout
CLITestHelper.TestingRuntime runtime = new CLITestHelper.TestingRuntime(true);

RunExampleExecutor executor = new RunExampleExecutor();
closeables.add(executor);

RunExampleTool tool = new RunExampleTool(executor, System.in, runtime);
try {
tool.runTool(SolrCLI.processCommandLineArgs(tool, toolArgs));
} catch (Exception e) {
System.err.println(
"RunExampleTool failed due to: "
+ e
+ "; stdout from tool prior to failure: "
+ runtime.getOutput());
throw e;
}

String toolOutput = runtime.getOutput();

// verify Solr is running on the expected port and verify the collection exists
String solrUrl = "http://localhost:" + bindPort + "/solr";
if (!CLIUtils.safeCheckCollectionExists(solrUrl, collectionName, null)) {
fail(
"After running Solr cloud example with --prompt-inputs, test collection '"
+ collectionName
+ "' not found in Solr at: "
+ solrUrl
+ "; tool output: "
+ toolOutput);
}

// verify the collection was created with the specified parameters
try (CloudSolrClient cloudClient =
new RandomizingCloudSolrClientBuilder(
Collections.singletonList(executor.solrCloudCluster.getZkServer().getZkAddress()),
Optional.empty())
.withDefaultCollection(collectionName)
.build()) {

// index some test docs to verify the collection works
int numDocs = 5;
for (int d = 0; d < numDocs; d++) {
SolrInputDocument doc = new SolrInputDocument();
doc.setField("id", "doc" + d);
doc.setField("test_s", "prompts");
cloudClient.add(doc);
}
cloudClient.commit();

QueryResponse qr = cloudClient.query(new SolrQuery("test_s:prompts"));
assertEquals(
"Expected "
+ numDocs
+ " docs in the "
+ collectionName
+ " collection created via --prompts",
numDocs,
qr.getResults().getNumFound());
}

// Verify output contains the prompts values
assertTrue(
"Tool output should contain the collection name", toolOutput.contains(collectionName));

// delete the collection
DeleteTool deleteTool = new DeleteTool(runtime);
String[] deleteArgs = new String[] {"--name", collectionName, "--solr-url", solrUrl};
deleteTool.runTool(SolrCLI.processCommandLineArgs(deleteTool, deleteArgs));

// stop the test instance
executor.execute(org.apache.commons.exec.CommandLine.parse("bin/solr stop -p " + bindPort));
}

@Test
public void testFailExecuteScript() throws Exception {
Path solrHomeDir = ExternalPaths.SERVER_HOME;
Expand Down
Loading
Loading