Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
fd1489d
Refactor Snapshot*Tools: extract params records and parser-independen…
janhoy Aug 3, 2026
ac17a19
Refactor PostLogsTool: extract PostLogsParams record
janhoy Aug 3, 2026
6ccb974
Refactor ApiTool: extract ApiParams record
janhoy Aug 3, 2026
cf89dd3
Refactor ClusterTool: extract ClusterParams and parser-independent se…
janhoy Aug 3, 2026
22ec3aa
Refactor ConfigTool: extract ConfigParams and parser-independent upda…
janhoy Aug 3, 2026
7a9019c
Refactor HealthcheckTool: extract HealthcheckParams, decouple runClou…
janhoy Aug 3, 2026
8577508
Refactor ExportTool: extract ExportParams and parser-independent expo…
janhoy Aug 3, 2026
a1bc741
Refactor PackageTool: extract parser-independent method per sub-command
janhoy Aug 3, 2026
9be1aa6
Refactor StreamTool: extract StreamParams and parser-independent methods
janhoy Aug 3, 2026
7f75214
Refactor PostTool: extract PostToolParams and parser-independent post…
janhoy Aug 3, 2026
64d5956
Refactor AssertTool: extract AssertParams, replace mutable statics wi…
janhoy Aug 3, 2026
b3d7217
Refactor RunExampleTool: extract RunExampleParams, decouple runExampl…
janhoy Aug 3, 2026
2193141
Use Option constant instead of String lookup in StreamToolTest (forbi…
janhoy Aug 3, 2026
7adecd4
StreamTool: friendly error for missing expression arg; fall back to b…
janhoy Aug 3, 2026
bf5c14f
AssertTool: replace nine assertion flag fields with a sealed Assertio…
janhoy Aug 4, 2026
a852321
RunExampleTool: split params into mode-specific records sharing Start…
janhoy Aug 4, 2026
0968fbc
PostTool: group params record into named ContentOptions, CrawlOptions…
janhoy Aug 4, 2026
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
14 changes: 12 additions & 2 deletions solr/core/src/java/org/apache/solr/cli/ApiTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class ApiTool extends ToolBase {
.desc("Send a GET request to a Solr API endpoint.")
.get();

/** Parameters for the api command, independent of the command line parser. */
record ApiParams(String getUrl, String credentials) {}

public ApiTool(ToolRuntime runtime) {
super(runtime);
}
Expand All @@ -63,8 +66,15 @@ public Options getOptions() {

@Override
public void runImpl(CommandLine cli) throws Exception {
String getUrl = cli.getOptionValue(SOLR_URL_OPTION);
String response = callGet(getUrl, cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION));
ApiParams params =
new ApiParams(
cli.getOptionValue(SOLR_URL_OPTION),
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION));
callApi(params);
}

void callApi(ApiParams params) throws Exception {
String response = callGet(params.getUrl(), params.credentials());

// pretty-print the response to stdout
echo(response);
Expand Down
136 changes: 102 additions & 34 deletions solr/core/src/java/org/apache/solr/cli/AssertTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.cli.CommandLine;
Expand All @@ -40,9 +41,9 @@
*/
public class AssertTool extends ToolBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static String message = null;
private static boolean useExitCode = false;
private static Long timeoutMs = 1000L;
private String message = null;
private boolean useExitCode = false;
private Long timeoutMs = 1000L;

private static final Option IS_NOT_ROOT_OPTION =
Option.builder().desc("Asserts that we are NOT the root user.").longOpt("not-root").get();
Expand Down Expand Up @@ -143,6 +144,50 @@ public class AssertTool extends ToolBase {
.longOpt("exitcode")
.get();

/** One requested assertion. Multiple assertions may be requested in a single invocation. */
sealed interface Assertion {
/** Asserts that we are the root user. */
record RootUser() implements Assertion {}

/** Asserts that we are NOT the root user. */
record NotRootUser() implements Assertion {}

/** Asserts that the directory exists. */
record DirExists(String dir) implements Assertion {}

/** Asserts that the directory does NOT exist. */
record DirNotExists(String dir) implements Assertion {}

/** Asserts that we run as the same user that owns the directory. */
record SameUser(String dir) implements Assertion {}

/** Asserts that Solr is running on the given URL. */
record SolrRunning(String url) implements Assertion {}

/** Asserts that Solr is NOT running on the given URL. */
record SolrNotRunning(String url) implements Assertion {}

/** Asserts that Solr on the given URL is running in cloud mode. */
record CloudMode(String url) implements Assertion {}

/** Asserts that Solr on the given URL is NOT running in cloud mode. */
record NotCloudMode(String url) implements Assertion {}
}

/**
* Parameters for the assert command, independent of the command line parser. URL values are the
Comment thread
janhoy marked this conversation as resolved.
* raw user input; they are normalized when the assertion runs.
*
* @param credentials credentials used by the URL-based assertions, or null
* @param assertions assertions to run, in order
*/
record AssertParams(
String message,
Long timeoutMs,
boolean useExitCode,
String credentials,
List<Assertion> assertions) {}

public AssertTool(ToolRuntime runtime) {
super(runtime);
}
Expand Down Expand Up @@ -210,49 +255,72 @@ public void runImpl(CommandLine cli) throws Exception {
* @throws Exception if a tool failed, e.g. authentication failure
*/
protected int runAssert(CommandLine cli) throws Exception {
message = cli.getOptionValue(MESSAGE_OPTION);
timeoutMs = cli.getParsedOptionValue(TIMEOUT_OPTION, timeoutMs);
useExitCode = cli.hasOption(EXIT_CODE_OPTION);

int ret = 0;
List<Assertion> assertions = new ArrayList<>();
if (cli.hasOption(IS_ROOT_OPTION)) {
ret += assertRootUser();
assertions.add(new Assertion.RootUser());
}
if (cli.hasOption(IS_NOT_ROOT_OPTION)) {
ret += assertNotRootUser();
assertions.add(new Assertion.NotRootUser());
}
if (cli.hasOption(DIRECTORY_EXISTS_OPTION)) {
ret += assertFileExists(cli.getOptionValue(DIRECTORY_EXISTS_OPTION));
assertions.add(new Assertion.DirExists(cli.getOptionValue(DIRECTORY_EXISTS_OPTION)));
}
if (cli.hasOption(DIRECTORY_NOT_EXISTS_OPTION)) {
ret += assertFileNotExists(cli.getOptionValue(DIRECTORY_NOT_EXISTS_OPTION));
assertions.add(new Assertion.DirNotExists(cli.getOptionValue(DIRECTORY_NOT_EXISTS_OPTION)));
}
if (cli.hasOption(SAME_USER_OPTION)) {
ret += sameUser(cli.getOptionValue(SAME_USER_OPTION));
assertions.add(new Assertion.SameUser(cli.getOptionValue(SAME_USER_OPTION)));
}
if (cli.hasOption(IS_RUNNING_ON_OPTION)) {
ret +=
assertSolrRunning(
cli.getOptionValue(IS_RUNNING_ON_OPTION),
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION));
assertions.add(new Assertion.SolrRunning(cli.getOptionValue(IS_RUNNING_ON_OPTION)));
}
if (cli.hasOption(IS_NOT_RUNNING_ON_OPTION)) {
ret +=
assertSolrNotRunning(
cli.getOptionValue(IS_NOT_RUNNING_ON_OPTION),
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION));
assertions.add(new Assertion.SolrNotRunning(cli.getOptionValue(IS_NOT_RUNNING_ON_OPTION)));
}
if (cli.hasOption(IS_CLOUD_OPTION)) {
ret +=
assertSolrRunningInCloudMode(
CLIUtils.normalizeSolrUrl(cli.getOptionValue(IS_CLOUD_OPTION)),
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION));
assertions.add(new Assertion.CloudMode(cli.getOptionValue(IS_CLOUD_OPTION)));
}
if (cli.hasOption(IS_NOT_CLOUD_OPTION)) {
assertions.add(new Assertion.NotCloudMode(cli.getOptionValue(IS_NOT_CLOUD_OPTION)));
}
return runAssert(
new AssertParams(
cli.getOptionValue(MESSAGE_OPTION),
cli.getParsedOptionValue(TIMEOUT_OPTION, timeoutMs),
cli.hasOption(EXIT_CODE_OPTION),
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION),
List.copyOf(assertions)));
}

/**
* Runs the requested assertions.
*
* @return 0 on success, or the number of assertions that failed
* @throws Exception if an assertion failed and exit codes are not used, e.g. authentication
* failure
*/
int runAssert(AssertParams params) throws Exception {
message = params.message();
timeoutMs = params.timeoutMs();
useExitCode = params.useExitCode();
String credentials = params.credentials();

int ret = 0;
for (Assertion assertion : params.assertions()) {
ret +=
assertSolrNotRunningInCloudMode(
CLIUtils.normalizeSolrUrl(cli.getOptionValue(IS_NOT_CLOUD_OPTION)),
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION));
switch (assertion) {
case Assertion.RootUser() -> assertRootUser();
case Assertion.NotRootUser() -> assertNotRootUser();
case Assertion.DirExists(String dir) -> assertFileExists(dir);
case Assertion.DirNotExists(String dir) -> assertFileNotExists(dir);
case Assertion.SameUser(String dir) -> sameUser(dir);
case Assertion.SolrRunning(String url) -> assertSolrRunning(url, credentials);
case Assertion.SolrNotRunning(String url) -> assertSolrNotRunning(url, credentials);
case Assertion.CloudMode(String url) ->
assertSolrRunningInCloudMode(CLIUtils.normalizeSolrUrl(url), credentials);
case Assertion.NotCloudMode(String url) ->
assertSolrNotRunningInCloudMode(CLIUtils.normalizeSolrUrl(url), credentials);
};
}
return ret;
}
Expand Down Expand Up @@ -344,7 +412,7 @@ public int assertSolrNotRunningInCloudMode(String url, String credentials) throw
return 0;
}

public static int sameUser(String directory) throws Exception {
public int sameUser(String directory) throws Exception {
Path path = Path.of(directory);
if (Files.exists(path)) {
String userForDir = userForDir(path);
Expand All @@ -357,28 +425,28 @@ public static int sameUser(String directory) throws Exception {
return 0;
}

public static int assertFileExists(String directory) throws Exception {
public int assertFileExists(String directory) throws Exception {
if (!Files.exists(Path.of(directory))) {
return exitOrException("Directory " + directory + " does not exist.");
}
return 0;
}

public static int assertFileNotExists(String directory) throws Exception {
public int assertFileNotExists(String directory) throws Exception {
if (Files.exists(Path.of(directory))) {
return exitOrException("Directory " + directory + " should not exist.");
}
return 0;
}

public static int assertRootUser() throws Exception {
public int assertRootUser() throws Exception {
if (!currentUser().equals("root")) {
return exitOrException("Must run as root user");
}
return 0;
}

public static int assertNotRootUser() throws Exception {
public int assertNotRootUser() throws Exception {
if (currentUser().equals("root")) {
return exitOrException("Not allowed to run as root user");
}
Expand All @@ -399,7 +467,7 @@ public static String userForDir(Path pathToDir) {
}
}

private static int exitOrException(String msg) throws AssertionFailureException {
private int exitOrException(String msg) throws AssertionFailureException {
if (useExitCode) {
return 1;
} else {
Expand Down
17 changes: 14 additions & 3 deletions solr/core/src/java/org/apache/solr/cli/ClusterTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public class ClusterTool extends ToolBase {
.desc("Set the property to this value.")
.get();

/** Parameters for the cluster command, independent of the command line parser. */
record ClusterParams(String propertyName, String propertyValue, String zkHost) {}

public ClusterTool(ToolRuntime runtime) {
super(runtime);
}
Expand All @@ -71,10 +74,18 @@ public Options getOptions() {

@Override
public void runImpl(CommandLine cli) throws Exception {
ClusterParams params =
new ClusterParams(
cli.getOptionValue(PROPERTY_OPTION),
cli.getOptionValue(VALUE_OPTION),
CLIUtils.getZkHost(cli));
setClusterProperty(params);
}

String propertyName = cli.getOptionValue(PROPERTY_OPTION);
String propertyValue = cli.getOptionValue(VALUE_OPTION);
String zkHost = CLIUtils.getZkHost(cli);
void setClusterProperty(ClusterParams params) throws Exception {
String propertyName = params.propertyName();
String propertyValue = params.propertyValue();
String zkHost = params.zkHost();

if (!ZkController.checkChrootPath(zkHost, true)) {
throw new IllegalStateException(
Expand Down
33 changes: 29 additions & 4 deletions solr/core/src/java/org/apache/solr/cli/ConfigTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ public class ConfigTool extends ToolBase {
.desc("Set the property to this value; accepts JSON objects and strings.")
.get();

/** Parameters for the config command, independent of the command line parser. */
record ConfigParams(
String solrUrl,
String action,
String collection,
String property,
String value,
String credentials) {}

public ConfigTool(ToolRuntime runtime) {
super(runtime);
}
Expand All @@ -96,14 +105,31 @@ public Options getOptions() {
public void runImpl(CommandLine cli) throws Exception {
String solrUrl = CLIUtils.normalizeSolrUrl(cli);
String action = cli.getOptionValue(ACTION_OPTION, "set-property");
String collection = cli.getOptionValue(COLLECTION_NAME_OPTION);
String property = cli.getOptionValue(PROPERTY_OPTION);
String value = cli.getOptionValue(VALUE_OPTION);

// value is required unless the property is one of the "unset-" type.
if (!action.contains("unset-") && value == null) {
throw new MissingArgumentException("'value' is a required option.");
}

ConfigParams params =
new ConfigParams(
solrUrl,
action,
cli.getOptionValue(COLLECTION_NAME_OPTION),
cli.getOptionValue(PROPERTY_OPTION),
value,
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION));
updateConfig(params);
}

void updateConfig(ConfigParams params) throws Exception {
String solrUrl = params.solrUrl();
String action = params.action();
String collection = params.collection();
String property = params.property();
String value = params.value();

Map<String, Object> jsonObj = new HashMap<>();
if (value != null) {
Map<String, String> setMap = new HashMap<>();
Expand All @@ -122,8 +148,7 @@ public void runImpl(CommandLine cli) throws Exception {
echo("\nPOSTing request to Config API: " + solrUrl + updatePath);
echoIfVerbose(jsonBody);

try (SolrClient solrClient =
CLIUtils.getSolrClient(solrUrl, cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION))) {
try (SolrClient solrClient = CLIUtils.getSolrClient(solrUrl, params.credentials())) {
NamedList<Object> result = SolrCLI.postJsonToSolr(solrClient, updatePath, jsonBody);
Integer statusCode = (Integer) result._get(List.of("responseHeader", "status"), null);
if (statusCode == 0) {
Expand Down
40 changes: 30 additions & 10 deletions solr/core/src/java/org/apache/solr/cli/ExportTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ public class ExportTool extends ToolBase {
.desc("Comma separated list of fields to export. By default all fields are fetched.")
.get();

/** Parameters for the export command, independent of the command line parser. */
record ExportParams(
String url,
String credentials,
String query,
String output,
String format,
boolean compress,
String fields,
String limit) {}

public ExportTool(ToolRuntime runtime) {
super(runtime);
}
Expand Down Expand Up @@ -292,16 +303,25 @@ public void runImpl(CommandLine cli) throws Exception {
throw new IllegalArgumentException(
"Must specify a connection target via -s/--solr-connection, --solr-url, or --zk-host.");
}
String credentials = cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION);
Info info = new MultiThreadedRunner(runtime, url, credentials);
info.query = cli.getOptionValue(QUERY_OPTION, "*:*");

info.setOutFormat(
cli.getOptionValue(OUTPUT_OPTION),
cli.getOptionValue(FORMAT_OPTION),
cli.hasOption(COMPRESS_OPTION));
info.fields = cli.getOptionValue(FIELDS_OPTION);
info.setLimit(cli.getOptionValue(LIMIT_OPTION, "100"));
ExportParams params =
new ExportParams(
url,
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION),
cli.getOptionValue(QUERY_OPTION, "*:*"),
cli.getOptionValue(OUTPUT_OPTION),
cli.getOptionValue(FORMAT_OPTION),
cli.hasOption(COMPRESS_OPTION),
cli.getOptionValue(FIELDS_OPTION),
cli.getOptionValue(LIMIT_OPTION, "100"));
export(params);
}

void export(ExportParams params) throws Exception {
Info info = new MultiThreadedRunner(runtime, params.url(), params.credentials());
info.query = params.query();
info.setOutFormat(params.output(), params.format(), params.compress());
info.fields = params.fields();
info.setLimit(params.limit());
info.exportDocs();
}

Expand Down
Loading
Loading