From 73c243c7bbc211187a34554d75d4f14e81d42043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 5 Apr 2026 04:00:56 +0200 Subject: [PATCH 1/7] Add picocli to project --- gradle/libs.versions.toml | 2 ++ solr/api/gradle.lockfile | 1 + solr/benchmark/gradle.lockfile | 1 + solr/core/build.gradle | 2 ++ solr/core/gradle.lockfile | 3 ++- solr/cross-dc-manager/gradle.lockfile | 1 + solr/modules/analysis-extras/gradle.lockfile | 1 + solr/modules/clustering/gradle.lockfile | 1 + solr/modules/cross-dc/gradle.lockfile | 1 + solr/modules/cuvs/gradle.lockfile | 1 + solr/modules/extraction/gradle.lockfile | 1 + solr/modules/gcs-repository/gradle.lockfile | 1 + solr/modules/jwt-auth/gradle.lockfile | 1 + solr/modules/langid/gradle.lockfile | 1 + solr/modules/language-models/gradle.lockfile | 1 + solr/modules/ltr/gradle.lockfile | 1 + solr/modules/opentelemetry/gradle.lockfile | 1 + solr/modules/s3-repository/gradle.lockfile | 1 + solr/modules/scripting/gradle.lockfile | 1 + solr/modules/sql/gradle.lockfile | 1 + solr/server/gradle.lockfile | 1 + solr/solr-ref-guide/gradle.lockfile | 1 + solr/solrj-jetty/gradle.lockfile | 1 + solr/solrj-streaming/gradle.lockfile | 1 + solr/solrj-zookeeper/gradle.lockfile | 1 + solr/solrj/gradle.lockfile | 1 + solr/test-framework/gradle.lockfile | 1 + solr/webapp/gradle.lockfile | 1 + 28 files changed, 31 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0bcff24a4457..a9222bc94c01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -186,6 +186,7 @@ ow2-asm = "9.8" owasp-dependencycheck = "12.1.3" # @keep for version alignment perfmark = "0.27.0" +picocli = "4.7.6" prometheus-metrics = "1.1.0" prometheus-simpleclient = "0.16.0" quicktheories = "0.26" @@ -500,6 +501,7 @@ ow2-asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "ow2-asm" ow2-asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "ow2-asm" } # @keep transitive dependency for version alignment perfmark-api = { module = "io.perfmark:perfmark-api", version.ref = "perfmark" } +picocli = { module = "info.picocli:picocli", version.ref = "picocli" } prometheus-metrics-expositionformats = { module = "io.prometheus:prometheus-metrics-exposition-formats", version.ref = "prometheus-metrics" } prometheus-metrics-model = { module = "io.prometheus:prometheus-metrics-model", version.ref = "prometheus-metrics" } prometheus-simpleclient = { module = "io.prometheus:simpleclient", version.ref = "prometheus-simpleclient" } diff --git a/solr/api/gradle.lockfile b/solr/api/gradle.lockfile index e244a4e4778f..5a35b2e643df 100644 --- a/solr/api/gradle.lockfile +++ b/solr/api/gradle.lockfile @@ -37,6 +37,7 @@ com.tdunning:t-digest:3.3=jarValidation,testRuntimeClasspath commons-cli:commons-cli:1.10.0=jarValidation,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,testRuntimeClasspath commons-io:commons-io:2.20.0=jarValidation,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/benchmark/gradle.lockfile b/solr/benchmark/gradle.lockfile index 950df2762f00..dcdf6be5b49f 100644 --- a/solr/benchmark/gradle.lockfile +++ b/solr/benchmark/gradle.lockfile @@ -33,6 +33,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,testRuntimeClasspath commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,runtimeClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,runtimeClasspath,testRuntimeClasspath diff --git a/solr/core/build.gradle b/solr/core/build.gradle index f1bb21be4aa0..144a7445f9c7 100644 --- a/solr/core/build.gradle +++ b/solr/core/build.gradle @@ -98,6 +98,8 @@ dependencies { implementation libs.commonscodec.commonscodec implementation libs.commonscli.commonscli + implementation libs.picocli + permitUnusedDeclared libs.picocli // will be used when CLI is migrated to picocli implementation libs.locationtech.spatial4j diff --git a/solr/core/gradle.lockfile b/solr/core/gradle.lockfile index c1aa2fd5e140..ec0442ee4662 100644 --- a/solr/core/gradle.lockfile +++ b/solr/core/gradle.lockfile @@ -37,6 +37,7 @@ com.tdunning:t-digest:3.3=compileClasspath,jarValidation,runtimeClasspath,runtim commons-cli:commons-cli:1.10.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath commons-codec:commons-codec:1.19.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=compileClasspath,jarValidation,permitUnusedDeclared,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath @@ -187,4 +188,4 @@ org.slf4j:jcl-over-slf4j:2.0.17=apiHelper,jarValidation,runtimeClasspath,runtime org.slf4j:slf4j-api:2.0.17=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath org.xerial.snappy:snappy-java:1.1.10.8=jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath ua.net.nlp:morfologik-ukrainian-search:4.9.1=jarValidation,testRuntimeClasspath -empty=apiHelperTest,missingdoclet,permitAggregatorUse,permitTestAggregatorUse,permitTestUnusedDeclared,permitTestUsedUndeclared,permitUnusedDeclared,permitUsedUndeclared,signatures +empty=apiHelperTest,missingdoclet,permitAggregatorUse,permitTestAggregatorUse,permitTestUnusedDeclared,permitTestUsedUndeclared,permitUsedUndeclared,signatures diff --git a/solr/cross-dc-manager/gradle.lockfile b/solr/cross-dc-manager/gradle.lockfile index b3fb845f81a6..8366a00d8972 100644 --- a/solr/cross-dc-manager/gradle.lockfile +++ b/solr/cross-dc-manager/gradle.lockfile @@ -52,6 +52,7 @@ commons-collections:commons-collections:3.2.2=jarValidation,runtimeClasspath,run commons-digester:commons-digester:2.1=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath commons-validator:commons-validator:1.7=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/analysis-extras/gradle.lockfile b/solr/modules/analysis-extras/gradle.lockfile index 9094e0c4c838..f16a120d6f00 100644 --- a/solr/modules/analysis-extras/gradle.lockfile +++ b/solr/modules/analysis-extras/gradle.lockfile @@ -35,6 +35,7 @@ com.tdunning:t-digest:3.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,s commons-cli:commons-cli:1.10.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/clustering/gradle.lockfile b/solr/modules/clustering/gradle.lockfile index 77e69877649f..0c4468e57f88 100644 --- a/solr/modules/clustering/gradle.lockfile +++ b/solr/modules/clustering/gradle.lockfile @@ -33,6 +33,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/cross-dc/gradle.lockfile b/solr/modules/cross-dc/gradle.lockfile index 8de3834cae8a..4cceb36b3540 100644 --- a/solr/modules/cross-dc/gradle.lockfile +++ b/solr/modules/cross-dc/gradle.lockfile @@ -34,6 +34,7 @@ com.tdunning:t-digest:3.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,s commons-cli:commons-cli:1.10.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/cuvs/gradle.lockfile b/solr/modules/cuvs/gradle.lockfile index 0900760dfbc9..3ddcf49613c1 100644 --- a/solr/modules/cuvs/gradle.lockfile +++ b/solr/modules/cuvs/gradle.lockfile @@ -35,6 +35,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/extraction/gradle.lockfile b/solr/modules/extraction/gradle.lockfile index 5c56a4a84f45..d6654292ad61 100644 --- a/solr/modules/extraction/gradle.lockfile +++ b/solr/modules/extraction/gradle.lockfile @@ -36,6 +36,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/gcs-repository/gradle.lockfile b/solr/modules/gcs-repository/gradle.lockfile index 5afe73626a3a..f9b4f8067bad 100644 --- a/solr/modules/gcs-repository/gradle.lockfile +++ b/solr/modules/gcs-repository/gradle.lockfile @@ -62,6 +62,7 @@ com.tdunning:t-digest:3.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,s commons-cli:commons-cli:1.10.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/jwt-auth/gradle.lockfile b/solr/modules/jwt-auth/gradle.lockfile index 519c53f94d5d..010d84b4250a 100644 --- a/solr/modules/jwt-auth/gradle.lockfile +++ b/solr/modules/jwt-auth/gradle.lockfile @@ -43,6 +43,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/langid/gradle.lockfile b/solr/modules/langid/gradle.lockfile index 55f8c65b26cb..7958267f6cc6 100644 --- a/solr/modules/langid/gradle.lockfile +++ b/solr/modules/langid/gradle.lockfile @@ -34,6 +34,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/language-models/gradle.lockfile b/solr/modules/language-models/gradle.lockfile index f9973282836f..ddb7e258d6cd 100644 --- a/solr/modules/language-models/gradle.lockfile +++ b/solr/modules/language-models/gradle.lockfile @@ -48,6 +48,7 @@ dev.langchain4j:langchain4j-http-client:1.9.1=jarValidation,runtimeClasspath,run dev.langchain4j:langchain4j-hugging-face:1.9.1-beta17=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath dev.langchain4j:langchain4j-mistral-ai:1.9.1=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath dev.langchain4j:langchain4j-open-ai:1.9.1=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/ltr/gradle.lockfile b/solr/modules/ltr/gradle.lockfile index 09c098e9d3fd..82d140e0e6a2 100644 --- a/solr/modules/ltr/gradle.lockfile +++ b/solr/modules/ltr/gradle.lockfile @@ -33,6 +33,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/opentelemetry/gradle.lockfile b/solr/modules/opentelemetry/gradle.lockfile index d952bc162f71..5bfb8f60825c 100644 --- a/solr/modules/opentelemetry/gradle.lockfile +++ b/solr/modules/opentelemetry/gradle.lockfile @@ -40,6 +40,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/s3-repository/gradle.lockfile b/solr/modules/s3-repository/gradle.lockfile index edca36eddb90..e11259d55458 100644 --- a/solr/modules/s3-repository/gradle.lockfile +++ b/solr/modules/s3-repository/gradle.lockfile @@ -46,6 +46,7 @@ com.tdunning:t-digest:3.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,s commons-cli:commons-cli:1.10.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/scripting/gradle.lockfile b/solr/modules/scripting/gradle.lockfile index fae41d9e6456..c0495633dee8 100644 --- a/solr/modules/scripting/gradle.lockfile +++ b/solr/modules/scripting/gradle.lockfile @@ -33,6 +33,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/modules/sql/gradle.lockfile b/solr/modules/sql/gradle.lockfile index 57f77bdfe53d..bcb290230a93 100644 --- a/solr/modules/sql/gradle.lockfile +++ b/solr/modules/sql/gradle.lockfile @@ -36,6 +36,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatfor commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/server/gradle.lockfile b/solr/server/gradle.lockfile index 9d5d42b6176a..b4e77c309a22 100644 --- a/solr/server/gradle.lockfile +++ b/solr/server/gradle.lockfile @@ -32,6 +32,7 @@ com.tdunning:t-digest:3.3=jarValidation,runtimeClasspath,solrCore commons-cli:commons-cli:1.10.0=jarValidation,runtimeClasspath,solrCore commons-codec:commons-codec:1.19.0=jarValidation,runtimeClasspath,solrCore commons-io:commons-io:2.20.0=jarValidation,runtimeClasspath,solrCore +info.picocli:picocli:4.7.6=jarValidation,runtimeClasspath,solrCore io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,runtimeClasspath,solrCore io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,errorprone,testAnnotationProcessor io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,errorprone,testAnnotationProcessor diff --git a/solr/solr-ref-guide/gradle.lockfile b/solr/solr-ref-guide/gradle.lockfile index 52404fd69b42..77bb2f2cb0a7 100644 --- a/solr/solr-ref-guide/gradle.lockfile +++ b/solr/solr-ref-guide/gradle.lockfile @@ -33,6 +33,7 @@ com.tdunning:t-digest:3.3=testRuntimeClasspath commons-cli:commons-cli:1.10.0=testRuntimeClasspath commons-codec:commons-codec:1.19.0=testRuntimeClasspath commons-io:commons-io:2.20.0=testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=testRuntimeClasspath diff --git a/solr/solrj-jetty/gradle.lockfile b/solr/solrj-jetty/gradle.lockfile index 862a0a52fd64..c8007848a888 100644 --- a/solr/solrj-jetty/gradle.lockfile +++ b/solr/solrj-jetty/gradle.lockfile @@ -33,6 +33,7 @@ com.tdunning:t-digest:3.3=jarValidation,testRuntimeClasspath commons-cli:commons-cli:1.10.0=jarValidation,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,testRuntimeClasspath commons-io:commons-io:2.20.0=jarValidation,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/solrj-streaming/gradle.lockfile b/solr/solrj-streaming/gradle.lockfile index d88dd24a13f4..2c0e91d0e14d 100644 --- a/solr/solrj-streaming/gradle.lockfile +++ b/solr/solrj-streaming/gradle.lockfile @@ -33,6 +33,7 @@ com.tdunning:t-digest:3.3=jarValidation,testRuntimeClasspath commons-cli:commons-cli:1.10.0=jarValidation,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,testRuntimeClasspath commons-io:commons-io:2.20.0=jarValidation,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/solrj-zookeeper/gradle.lockfile b/solr/solrj-zookeeper/gradle.lockfile index 687e58c37caa..298846a0a5cc 100644 --- a/solr/solrj-zookeeper/gradle.lockfile +++ b/solr/solrj-zookeeper/gradle.lockfile @@ -38,6 +38,7 @@ commons-cli:commons-cli:1.10.0=jarValidation,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,testRuntimeClasspath commons-io:commons-io:2.17.0=apiHelper commons-io:commons-io:2.20.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/solrj/gradle.lockfile b/solr/solrj/gradle.lockfile index 363bc0de923d..c90961febe87 100644 --- a/solr/solrj/gradle.lockfile +++ b/solr/solrj/gradle.lockfile @@ -39,6 +39,7 @@ com.tdunning:t-digest:3.3=jarValidation,testRuntimeClasspath commons-cli:commons-cli:1.10.0=jarValidation,testRuntimeClasspath commons-codec:commons-codec:1.19.0=jarValidation,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=jarValidation,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/test-framework/gradle.lockfile b/solr/test-framework/gradle.lockfile index 2ae0de12ce0c..b97f50c3595d 100644 --- a/solr/test-framework/gradle.lockfile +++ b/solr/test-framework/gradle.lockfile @@ -34,6 +34,7 @@ com.tdunning:t-digest:3.3=apiHelper,jarValidation,runtimeClasspath,testRuntimeCl commons-cli:commons-cli:1.10.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath commons-codec:commons-codec:1.19.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli:4.7.6=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath diff --git a/solr/webapp/gradle.lockfile b/solr/webapp/gradle.lockfile index ea94512eff69..fcd5f5c40905 100644 --- a/solr/webapp/gradle.lockfile +++ b/solr/webapp/gradle.lockfile @@ -32,6 +32,7 @@ com.tdunning:t-digest:3.3=solrCore commons-cli:commons-cli:1.10.0=solrCore commons-codec:commons-codec:1.19.0=solrCore commons-io:commons-io:2.20.0=solrCore +info.picocli:picocli:4.7.6=solrCore io.dropwizard.metrics:metrics-core:4.2.26=solrCore io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,errorprone,testAnnotationProcessor io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,errorprone,testAnnotationProcessor From 3cbac7428792e28833ee7dad522719af553e0bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 5 Apr 2026 14:49:44 +0200 Subject: [PATCH 2/7] Add sha and license for picocli --- solr/licenses/picocli-4.7.6.jar.sha1 | 1 + solr/licenses/picocli-LICENSE-ASL.txt | 201 ++++++++++++++++++++++++++ solr/licenses/picocli-NOTICE.txt | 2 + 3 files changed, 204 insertions(+) create mode 100644 solr/licenses/picocli-4.7.6.jar.sha1 create mode 100644 solr/licenses/picocli-LICENSE-ASL.txt create mode 100644 solr/licenses/picocli-NOTICE.txt diff --git a/solr/licenses/picocli-4.7.6.jar.sha1 b/solr/licenses/picocli-4.7.6.jar.sha1 new file mode 100644 index 000000000000..c63f30646a6d --- /dev/null +++ b/solr/licenses/picocli-4.7.6.jar.sha1 @@ -0,0 +1 @@ +77c2cb87814b6a03d431fc856024a9f8ff605ad4 diff --git a/solr/licenses/picocli-LICENSE-ASL.txt b/solr/licenses/picocli-LICENSE-ASL.txt new file mode 100644 index 000000000000..9c8f3ea0871e --- /dev/null +++ b/solr/licenses/picocli-LICENSE-ASL.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/solr/licenses/picocli-NOTICE.txt b/solr/licenses/picocli-NOTICE.txt new file mode 100644 index 000000000000..d77f6f044d44 --- /dev/null +++ b/solr/licenses/picocli-NOTICE.txt @@ -0,0 +1,2 @@ +Picocli is licensed under the Apache License, Version 2.0. +Copyright (c) 2015-2025 Remko Popma and contributors. \ No newline at end of file From cb41a6944445544423fed35c99e2da3d40ad9936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 5 Apr 2026 14:49:59 +0200 Subject: [PATCH 3/7] Add changelog entry for SOLR-17697 --- changelog/unreleased/SOLR-17697-picocli.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog/unreleased/SOLR-17697-picocli.yml diff --git a/changelog/unreleased/SOLR-17697-picocli.yml b/changelog/unreleased/SOLR-17697-picocli.yml new file mode 100644 index 000000000000..23fd96984011 --- /dev/null +++ b/changelog/unreleased/SOLR-17697-picocli.yml @@ -0,0 +1,8 @@ +title: Add picocli as a dependency to enable migration of CLI tools away from commons-cli +type: other +authors: + - name: Jan Høydahl + url: https://home.apache.org/phonebook.html?uid=janhoy +links: + - name: SOLR-17697 + url: https://issues.apache.org/jira/browse/SOLR-17697 From 79feaf534c9fdd9a519e7627964c0fb35223df90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 5 Apr 2026 22:33:06 +0200 Subject: [PATCH 4/7] Enable picocli for start, stop, status, version and zk ls --- gradle/java/javac.gradle | 3 +- gradle/libs.versions.toml | 1 + solr/bin/solr | 6 +- solr/bin/solr.cmd | 18 +- solr/core/build.gradle | 2 +- solr/core/gradle.lockfile | 3 +- .../src/java/org/apache/solr/cli/ApiTool.java | 5 + .../java/org/apache/solr/cli/AssertTool.java | 5 + .../java/org/apache/solr/cli/AuthTool.java | 5 + .../java/org/apache/solr/cli/CLIUtils.java | 1 + .../solr/cli/CliDefaultValueProvider.java | 78 ++++++ .../java/org/apache/solr/cli/ClusterTool.java | 5 + .../solr/cli/ConfigSetDownloadTool.java | 5 + .../apache/solr/cli/ConfigSetUploadTool.java | 5 + .../java/org/apache/solr/cli/ConfigTool.java | 5 + .../java/org/apache/solr/cli/CreateTool.java | 5 + .../java/org/apache/solr/cli/DeleteTool.java | 5 + .../java/org/apache/solr/cli/ExportTool.java | 5 + .../org/apache/solr/cli/HealthcheckTool.java | 5 + .../java/org/apache/solr/cli/PackageTool.java | 5 + .../org/apache/solr/cli/PostLogsTool.java | 5 + .../java/org/apache/solr/cli/PostTool.java | 5 + .../org/apache/solr/cli/RunExampleTool.java | 5 + .../apache/solr/cli/SnapshotCreateTool.java | 5 + .../apache/solr/cli/SnapshotDeleteTool.java | 5 + .../apache/solr/cli/SnapshotDescribeTool.java | 5 + .../apache/solr/cli/SnapshotExportTool.java | 5 + .../org/apache/solr/cli/SnapshotListTool.java | 5 + .../src/java/org/apache/solr/cli/SolrCLI.java | 105 +++++++- .../org/apache/solr/cli/StartCommand.java | 228 ++++++++++++++++++ .../java/org/apache/solr/cli/StatusTool.java | 120 ++++++--- .../java/org/apache/solr/cli/StopCommand.java | 50 ++++ .../java/org/apache/solr/cli/StreamTool.java | 5 + .../src/java/org/apache/solr/cli/Tool.java | 6 + .../java/org/apache/solr/cli/ToolBase.java | 41 +++- .../org/apache/solr/cli/UpdateACLTool.java | 5 + .../java/org/apache/solr/cli/VersionTool.java | 15 ++ .../apache/solr/cli/ZkConnectionOptions.java | 100 ++++++++ .../java/org/apache/solr/cli/ZkCpTool.java | 5 + .../java/org/apache/solr/cli/ZkLsTool.java | 53 +++- .../org/apache/solr/cli/ZkMkrootTool.java | 5 + .../java/org/apache/solr/cli/ZkMvTool.java | 5 + .../java/org/apache/solr/cli/ZkRmTool.java | 5 + .../src/java/org/apache/solr/cli/ZkTool.java | 40 +++ .../java/org/apache/solr/cli/ZkToolHelp.java | 5 + solr/test-framework/build.gradle | 4 + solr/test-framework/gradle.lockfile | 5 +- 47 files changed, 957 insertions(+), 57 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/cli/CliDefaultValueProvider.java create mode 100644 solr/core/src/java/org/apache/solr/cli/StartCommand.java create mode 100644 solr/core/src/java/org/apache/solr/cli/StopCommand.java create mode 100644 solr/core/src/java/org/apache/solr/cli/ZkConnectionOptions.java create mode 100644 solr/core/src/java/org/apache/solr/cli/ZkTool.java diff --git a/gradle/java/javac.gradle b/gradle/java/javac.gradle index 8a7af8b04d70..3e992b1200ec 100644 --- a/gradle/java/javac.gradle +++ b/gradle/java/javac.gradle @@ -60,7 +60,8 @@ allprojects { "-Xlint:text-blocks", "-proc:none", // proc:none was added because of LOG4J2-1925 / JDK-8186647 "-Xlint:removal", - "--should-stop=ifError=FLOW" // error-prone 2.41 + "--should-stop=ifError=FLOW", // error-prone 2.41 + "-Aproject=${project.group}/${project.name}" ] if (propertyOrDefault("javac.failOnWarnings", true).toBoolean()) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a9222bc94c01..4a7684d42713 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -502,6 +502,7 @@ ow2-asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "ow2-asm" } # @keep transitive dependency for version alignment perfmark-api = { module = "io.perfmark:perfmark-api", version.ref = "perfmark" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" } +picocli-codegen = { module = "info.picocli:picocli-codegen", version.ref = "picocli" } prometheus-metrics-expositionformats = { module = "io.prometheus:prometheus-metrics-exposition-formats", version.ref = "prometheus-metrics" } prometheus-metrics-model = { module = "io.prometheus:prometheus-metrics-model", version.ref = "prometheus-metrics" } prometheus-simpleclient = { module = "io.prometheus:simpleclient", version.ref = "prometheus-simpleclient" } diff --git a/solr/bin/solr b/solr/bin/solr index eaa417ca90d0..387e942ca5aa 100755 --- a/solr/bin/solr +++ b/solr/bin/solr @@ -812,7 +812,11 @@ if [ $# -gt 0 ]; then shift 2 ;; -h|--help) - print_usage "$SCRIPT_CMD" + if [[ "${SOLR_PICOCLI:-}" == "true" ]]; then + run_tool "$SCRIPT_CMD" --help + else + print_usage "$SCRIPT_CMD" + fi exit 0 ;; -y|--no-prompt) diff --git a/solr/bin/solr.cmd b/solr/bin/solr.cmd index e30cc4416fa8..80097d5da307 100755 --- a/solr/bin/solr.cmd +++ b/solr/bin/solr.cmd @@ -414,8 +414,22 @@ IF "%1"=="--all" goto set_stop_all :parse_general_args REM Print usage of command in case help option included -IF "%1"=="--help" goto usage -IF "%1"=="-h" goto usage +IF "%1"=="--help" goto check_picocli_help +IF "%1"=="-h" goto check_picocli_help +goto after_help_check + +:check_picocli_help +IF "%SOLR_PICOCLI%"=="true" goto run_picocli_help +goto usage + +:run_picocli_help +"%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^ + -Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^ + -classpath "%SOLR_TIP%\lib\*;%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^ + org.apache.solr.cli.SolrCLI %SCRIPT_CMD% --help +goto done + +:after_help_check REM other args supported by all special commands IF "%1"=="-p" goto set_port diff --git a/solr/core/build.gradle b/solr/core/build.gradle index 144a7445f9c7..398a0a2a2ca4 100644 --- a/solr/core/build.gradle +++ b/solr/core/build.gradle @@ -99,7 +99,7 @@ dependencies { implementation libs.commonscli.commonscli implementation libs.picocli - permitUnusedDeclared libs.picocli // will be used when CLI is migrated to picocli + annotationProcessor libs.picocli.codegen implementation libs.locationtech.spatial4j diff --git a/solr/core/gradle.lockfile b/solr/core/gradle.lockfile index ec0442ee4662..512297f3c9a9 100644 --- a/solr/core/gradle.lockfile +++ b/solr/core/gradle.lockfile @@ -37,7 +37,8 @@ com.tdunning:t-digest:3.3=compileClasspath,jarValidation,runtimeClasspath,runtim commons-cli:commons-cli:1.10.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath commons-codec:commons-codec:1.19.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath -info.picocli:picocli:4.7.6=compileClasspath,jarValidation,permitUnusedDeclared,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath +info.picocli:picocli-codegen:4.7.6=annotationProcessor +info.picocli:picocli:4.7.6=annotationProcessor,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=jarValidation,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=jarValidation,testRuntimeClasspath diff --git a/solr/core/src/java/org/apache/solr/cli/ApiTool.java b/solr/core/src/java/org/apache/solr/cli/ApiTool.java index 4a9c86fb8485..b5679429d1ac 100644 --- a/solr/core/src/java/org/apache/solr/cli/ApiTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ApiTool.java @@ -117,4 +117,9 @@ public static ModifiableSolrParams getSolrParamsFromUri(URI uri) { } return paramsMap; } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/AssertTool.java b/solr/core/src/java/org/apache/solr/cli/AssertTool.java index 928065f0a798..064545f9fd3d 100644 --- a/solr/core/src/java/org/apache/solr/cli/AssertTool.java +++ b/solr/core/src/java/org/apache/solr/cli/AssertTool.java @@ -426,6 +426,11 @@ private static boolean runningSolrIsCloud(String url, String credentials) throws } } + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } + public static class AssertionFailureException extends Exception { public AssertionFailureException(String message) { super(message); diff --git a/solr/core/src/java/org/apache/solr/cli/AuthTool.java b/solr/core/src/java/org/apache/solr/cli/AuthTool.java index 5c19ac300d7f..606086529180 100644 --- a/solr/core/src/java/org/apache/solr/cli/AuthTool.java +++ b/solr/core/src/java/org/apache/solr/cli/AuthTool.java @@ -452,4 +452,9 @@ public void runImpl(CommandLine cli) throws Exception { throw new IllegalStateException("Only type=basicAuth supported at the moment."); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java index 5653bffa8970..48531f0ac16b 100644 --- a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java +++ b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java @@ -183,6 +183,7 @@ public static String normalizeSolrUrl(String solrUrl, boolean logUrlFormatWarnin * Get the base URL of a live Solr instance from either the --solr-url command-line option or from * ZooKeeper. */ + @Deprecated public static String normalizeSolrUrl(CommandLine cli) throws Exception { String solrUrl = cli.getOptionValue(CommonCLIOptions.SOLR_URL_OPTION); diff --git a/solr/core/src/java/org/apache/solr/cli/CliDefaultValueProvider.java b/solr/core/src/java/org/apache/solr/cli/CliDefaultValueProvider.java new file mode 100644 index 000000000000..99cb2ab95564 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/CliDefaultValueProvider.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import static org.apache.solr.cli.CLIUtils.getCloudSolrClient; +import static org.apache.solr.cli.CLIUtils.normalizeSolrUrl; + +import java.util.Set; +import org.apache.solr.client.solrj.impl.CloudSolrClient; +import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.common.util.EnvUtils; +import picocli.CommandLine; + +/** Provides default values for CLI arguments. */ +public class CliDefaultValueProvider implements CommandLine.IDefaultValueProvider { + @Override + public String defaultValue(CommandLine.Model.ArgSpec argSpec) throws Exception { + return switch (argSpec.paramLabel()) { + case "" -> EnvUtils.getProperty("zkHost"); + case "" -> { + String val = EnvUtils.getProperty("solr.url"); + yield val != null ? val : resolveSolrUrlViaZkHost(argSpec); + } + case "" -> EnvUtils.getProperty("solr.port", "8983"); + case "" -> EnvUtils.getProperty("solr.max.wait.seconds", "0"); + default -> null; + }; + } + + /** + * If no solrUrl is provided on the command line, and SOLR_URL is not set, this method will be + * used to determine the solrUrl from the zkHost. + * + * @param argSpec the argSpec for the solrUrl option + * @return the solrUrl + * @throws Exception if an error occurs + */ + public static String resolveSolrUrlViaZkHost(picocli.CommandLine.Model.ArgSpec argSpec) + throws Exception { + // Find value of zkHost from command line options. The argSpec passed in will be for the + // solrUrl option. + CommandLine.Model.OptionSpec zkHostOption = argSpec.command().findOption("--zk-host"); + + String zkHost = zkHostOption != null ? zkHostOption.getValue() : null; + if (zkHost == null) { + return null; + } + + String solrUrl; + try (CloudSolrClient cloudSolrClient = getCloudSolrClient(zkHost)) { + cloudSolrClient.connect(); + Set liveNodes = cloudSolrClient.getClusterState().getLiveNodes(); + if (liveNodes.isEmpty()) + throw new IllegalStateException( + "No live nodes found! Cannot determine 'solrUrl' from ZooKeeper: " + zkHost); + + String firstLiveNode = liveNodes.iterator().next(); + solrUrl = ZkStateReader.from(cloudSolrClient).getBaseUrlForNodeName(firstLiveNode); + solrUrl = normalizeSolrUrl(solrUrl, false); + } + solrUrl = normalizeSolrUrl(solrUrl); + return solrUrl; + } +} diff --git a/solr/core/src/java/org/apache/solr/cli/ClusterTool.java b/solr/core/src/java/org/apache/solr/cli/ClusterTool.java index 54626e2b7db7..aa70d0582fef 100644 --- a/solr/core/src/java/org/apache/solr/cli/ClusterTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ClusterTool.java @@ -97,4 +97,9 @@ public void runImpl(CommandLine cli) throws Exception { } } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java b/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java index 5623794626cb..a20c5bff853f 100644 --- a/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java @@ -101,4 +101,9 @@ public void runImpl(CommandLine cli) throws Exception { throw (e); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java b/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java index fdd2380f3a19..ba3f188b0b9a 100644 --- a/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java @@ -107,4 +107,9 @@ public void runImpl(CommandLine cli) throws Exception { throw (e); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigTool.java b/solr/core/src/java/org/apache/solr/cli/ConfigTool.java index a4145168165b..b2a340ba7ed2 100644 --- a/solr/core/src/java/org/apache/solr/cli/ConfigTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ConfigTool.java @@ -137,4 +137,9 @@ public void runImpl(CommandLine cli) throws Exception { } } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/CreateTool.java b/solr/core/src/java/org/apache/solr/cli/CreateTool.java index 74778e7e4904..1f81609f0b78 100644 --- a/solr/core/src/java/org/apache/solr/cli/CreateTool.java +++ b/solr/core/src/java/org/apache/solr/cli/CreateTool.java @@ -352,4 +352,9 @@ private void printDefaultConfigsetWarningIfNecessary(CommandLine cli) { echo(" " + configCommand); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java index 77db2184a4cb..07f92894f311 100644 --- a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java +++ b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java @@ -216,4 +216,9 @@ protected void deleteCore(CommandLine cli, SolrClient solrClient) throws Excepti throw new Exception("Failed to delete core '" + coreName + "' due to: " + sse.getMessage()); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ExportTool.java b/solr/core/src/java/org/apache/solr/cli/ExportTool.java index 6fd43b52a1c8..e95af8640baf 100644 --- a/solr/core/src/java/org/apache/solr/cli/ExportTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ExportTool.java @@ -707,4 +707,9 @@ static long getDocCount(String coreName, SolrClient client, String query) SolrDocumentList sdl = (SolrDocumentList) res.get("response"); return sdl.getNumFound(); } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java index 670bb0eb6563..2eea27280936 100644 --- a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java +++ b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java @@ -213,6 +213,11 @@ protected void runCloudTool(CloudSolrClient cloudSolrClient, CommandLine cli) th new JSONWriter(arr, 2).write(report); echo(arr.toString()); } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } class ReplicaHealth implements Comparable { diff --git a/solr/core/src/java/org/apache/solr/cli/PackageTool.java b/solr/core/src/java/org/apache/solr/cli/PackageTool.java index aaa3649d1be8..9dea8aaba9e9 100644 --- a/solr/core/src/java/org/apache/solr/cli/PackageTool.java +++ b/solr/core/src/java/org/apache/solr/cli/PackageTool.java @@ -378,4 +378,9 @@ public Options getOptions() { .addOption(CommonCLIOptions.CREDENTIALS_OPTION) .addOptionGroup(getConnectionOptions()); } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java index 82dccf537427..64a99784dacb 100644 --- a/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java +++ b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java @@ -626,4 +626,9 @@ public static String[] getRequestPurposeNames(Integer reqPurpose) { map.put(ShardRequest.PURPOSE_GET_TERM_STATS, "GET_TERM_STATS"); purposes = Collections.unmodifiableMap(map); } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/PostTool.java b/solr/core/src/java/org/apache/solr/cli/PostTool.java index 97751490e3f4..bf8832ad6785 100644 --- a/solr/core/src/java/org/apache/solr/cli/PostTool.java +++ b/solr/core/src/java/org/apache/solr/cli/PostTool.java @@ -1342,6 +1342,11 @@ protected Set getLinksFromWebPage(URL url, InputStream is, String type, URI } } + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } + /** Utility class to hold the result form a page fetch */ public static class PageFetcherResult { int httpStatus = 200; diff --git a/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java b/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java index e1d002cf21ee..d6c120be041f 100644 --- a/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java +++ b/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java @@ -1079,6 +1079,11 @@ protected void copyIfNeeded(Path src, Path dest) throws IOException { throw new IllegalStateException("Required file " + dest.toAbsolutePath() + " not found!"); } + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } + protected boolean isPortAvailable(int port) { try (Socket s = new Socket("localhost", port)) { assert s != null; // To allow compilation. diff --git a/solr/core/src/java/org/apache/solr/cli/SnapshotCreateTool.java b/solr/core/src/java/org/apache/solr/cli/SnapshotCreateTool.java index dfc39bf7cb2d..893d57e4c6c7 100644 --- a/solr/core/src/java/org/apache/solr/cli/SnapshotCreateTool.java +++ b/solr/core/src/java/org/apache/solr/cli/SnapshotCreateTool.java @@ -97,4 +97,9 @@ public void createSnapshot(SolrClient solrClient, String collectionName, String + e.getLocalizedMessage()); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/SnapshotDeleteTool.java b/solr/core/src/java/org/apache/solr/cli/SnapshotDeleteTool.java index 00b5c3c01979..5092d87b68f6 100644 --- a/solr/core/src/java/org/apache/solr/cli/SnapshotDeleteTool.java +++ b/solr/core/src/java/org/apache/solr/cli/SnapshotDeleteTool.java @@ -97,4 +97,9 @@ public void deleteSnapshot(SolrClient solrClient, String collectionName, String + e.getLocalizedMessage()); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/SnapshotDescribeTool.java b/solr/core/src/java/org/apache/solr/cli/SnapshotDescribeTool.java index 477ad265e7d5..3b5102d50796 100644 --- a/solr/core/src/java/org/apache/solr/cli/SnapshotDescribeTool.java +++ b/solr/core/src/java/org/apache/solr/cli/SnapshotDescribeTool.java @@ -136,4 +136,9 @@ private Collection listCollectionSnapshots( return result; } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/SnapshotExportTool.java b/solr/core/src/java/org/apache/solr/cli/SnapshotExportTool.java index d71b8df8b1a2..6b2406fd80e2 100644 --- a/solr/core/src/java/org/apache/solr/cli/SnapshotExportTool.java +++ b/solr/core/src/java/org/apache/solr/cli/SnapshotExportTool.java @@ -131,4 +131,9 @@ public void exportSnapshot( + e.getLocalizedMessage()); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/SnapshotListTool.java b/solr/core/src/java/org/apache/solr/cli/SnapshotListTool.java index 4501fddc8397..eedad418a1c0 100644 --- a/solr/core/src/java/org/apache/solr/cli/SnapshotListTool.java +++ b/solr/core/src/java/org/apache/solr/cli/SnapshotListTool.java @@ -85,4 +85,9 @@ public void listSnapshots(SolrClient solrClient, String collectionName) { + e.getLocalizedMessage()); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java index 9f34c32cb371..6a6828161ff9 100755 --- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java +++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java @@ -42,9 +42,11 @@ import org.apache.commons.cli.help.HelpFormatter; import org.apache.commons.cli.help.TableDefinition; import org.apache.commons.cli.help.TextHelpAppendable; +import org.apache.solr.client.api.util.SolrVersion; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest; import org.apache.solr.common.util.ContentStreamBase; +import org.apache.solr.common.util.EnvUtils; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SuppressForbidden; import org.apache.solr.util.configuration.SSLConfigurationsFactory; @@ -52,14 +54,92 @@ import org.slf4j.LoggerFactory; /** Command-line utility for working with Solr. */ +@picocli.CommandLine.Command( + name = "solr", + version = "Apache Solr version " + SolrVersion.LATEST_STRING, + mixinStandardHelpOptions = true, + commandListHeading = "\nCommands:\n", + descriptionHeading = "Global options:\n", + footer = { + "", + "SolrCloud example (embedded Zookeeper):", + "", + " ./solr start", + "", + "Get help for a command by running 'solr COMMAND --help'.", + "", + "For more help on how to use Solr, head to https://solr.apache.org/" + }, + usageHelpAutoWidth = true, + usageHelpWidth = 120, + defaultValueProvider = CliDefaultValueProvider.class, + subcommands = { + StartCommand.class, + StopCommand.class, + StatusTool.class, + VersionTool.class, + ZkTool.class + }) public class SolrCLI implements CLIO { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @SuppressForbidden(reason = "SolrCLI is a CLI entry point; System.exit is required here") + public static void exit(int exitStatus) { + try { + System.exit(exitStatus); + } catch (java.lang.SecurityException secExc) { + if (exitStatus != 0) + throw new RuntimeException("SolrCLI failed to exit with status " + exitStatus); + } + } + /** Runs a tool. */ public static void main(String[] args) throws Exception { - ToolRuntime runtime = new DefaultToolRuntime(); + if (EnvUtils.getPropertyAsBool("solr.picocli", false)) { + SSLConfigurationsFactory.current().init(); + picocli.CommandLine commandLine = new picocli.CommandLine(new SolrCLI()); + propagateCommandSettings(commandLine); + exit(commandLine.execute(args)); + } else { + exit(parseWithCommonsCli(args)); + } + } + + /** Propagates common settings to all subcommands. */ + private static void propagateCommandSettings(picocli.CommandLine cmd) { + for (picocli.CommandLine subcommand : cmd.getSubcommands().values()) { + subcommand.getCommandSpec().defaultValueProvider(cmd.getCommandSpec().defaultValueProvider()); + subcommand + .getCommandSpec() + .mixinStandardHelpOptions(cmd.getCommandSpec().mixinStandardHelpOptions()); + subcommand.getCommandSpec().usageMessage().width(cmd.getCommandSpec().usageMessage().width()); + subcommand + .getCommandSpec() + .usageMessage() + .autoWidth(cmd.getCommandSpec().usageMessage().autoWidth()); + subcommand + .getCommandSpec() + .usageMessage() + .commandListHeading(cmd.getCommandSpec().usageMessage().commandListHeading()); + subcommand + .getCommandSpec() + .usageMessage() + .footer( + "\nFor a full CLI reference, see https://solr.apache.org/guide/solr/latest/deployment-guide/solr-control-script-reference.html"); + propagateCommandSettings(subcommand); + } + } + /** + * Parses the command-line arguments passed by the user using Apache Commons CLI. This + * + * @param args the original command-line arguments + * @deprecated Please use picocli + */ + @Deprecated(since = "10.1") + public static int parseWithCommonsCli(String[] args) throws Exception { + ToolRuntime runtime = new DefaultToolRuntime(); final boolean hasNoCommand = args == null || args.length == 0 || args[0] == null || args[0].trim().isEmpty(); final boolean isHelpCommand = !hasNoCommand && Arrays.asList("-h", "--help").contains(args[0]); @@ -114,7 +194,7 @@ public static void main(String[] args) throws Exception { runtime.exit(1); } CommandLine cli = parseCmdLine(tool, args); - runtime.exit(tool.runTool(cli)); + return tool.runTool(cli); } public static Tool findTool(String[] args, ToolRuntime runtime) throws Exception { @@ -122,6 +202,7 @@ public static Tool findTool(String[] args, ToolRuntime runtime) throws Exception return newTool(toolType, runtime); } + @Deprecated public static CommandLine parseCmdLine(Tool tool, String[] args) throws IOException { // the parser doesn't like -D props List toolArgList = new ArrayList<>(); @@ -137,7 +218,7 @@ public static CommandLine parseCmdLine(Tool tool, String[] args) throws IOExcept String[] toolArgs = toolArgList.toArray(new String[0]); // process command-line args to configure this application - CommandLine cli = processCommandLineArgs(tool, toolArgs); + org.apache.commons.cli.CommandLine cli = processCommandLineArgs(tool, toolArgs); List argList = cli.getArgList(); argList.addAll(dashDList); @@ -176,6 +257,7 @@ protected static void checkSslStoreSysProp(String solrInstallDir, String key) { } // Creates an instance of the requested tool, using classpath scanning if necessary + @Deprecated private static Tool newTool(String toolType, ToolRuntime runtime) throws Exception { if ("healthcheck".equals(toolType)) return new HealthcheckTool(runtime); else if ("status".equals(toolType)) return new StatusTool(runtime); @@ -228,7 +310,7 @@ private static Tool newTool(String toolType, ToolRuntime runtime) throws Excepti * CLI option. */ public static String getOptionWithDeprecatedAndDefault( - CommandLine cli, Option opt, Option deprecated, String def) { + org.apache.commons.cli.CommandLine cli, Option opt, Option deprecated, String def) { String val = cli.getOptionValue(opt); if (val == null) { val = cli.getOptionValue(deprecated); @@ -238,6 +320,7 @@ public static String getOptionWithDeprecatedAndDefault( // TODO: SOLR-17429 - remove the custom logic when Commons CLI is upgraded and // makes stderr the default, or makes Option.toDeprecatedString() public. + @Deprecated private static void deprecatedHandlerStdErr(Option o) { // Deprecated options without a description act as "stealth" options if (o.isDeprecated() && !o.getDeprecated().getDescription().isBlank()) { @@ -252,11 +335,12 @@ private static void deprecatedHandlerStdErr(Option o) { } /** Parses the command-line arguments passed by the user. */ + @Deprecated public static CommandLine processCommandLineArgs(Tool tool, String[] args) throws IOException { Options options = tool.getOptions(); ToolRuntime runtime = tool.getRuntime(); - CommandLine cli = null; + org.apache.commons.cli.CommandLine cli = null; try { cli = DefaultParser.builder() @@ -293,6 +377,7 @@ public static CommandLine processCommandLineArgs(Tool tool, String[] args) throw } /** Prints tool help for a given tool */ + @Deprecated public static void printToolHelp(Tool tool) throws IOException { HelpFormatter formatter = getFormatter(); Options nonDeprecatedOptions = new Options(); @@ -311,6 +396,7 @@ public static void printToolHelp(Tool tool) throws IOException { autoGenerateUsage); } + @Deprecated @SuppressForbidden(reason = "System.out for formatting") public static HelpFormatter getFormatter() { TextHelpAppendable helpAppendable = @@ -346,7 +432,12 @@ public void appendParagraph(CharSequence paragraph) throws IOException { return formatter; } - /** Scans Jar files on the classpath for Tool implementations to activate. */ + /** + * Scans Jar files on the classpath for Tool implementations to activate. + * + * @deprecated With Picocli we no longer need to scan the classpath for Tool implementations? + */ + @Deprecated private static List> findToolClassesInPackage(String packageName) { List> toolClasses = new ArrayList<>(); try { @@ -370,6 +461,7 @@ private static List> findToolClassesInPackage(String packa return toolClasses; } + @Deprecated private static Set findClasses(String path, String packageName) throws Exception { Set classes = new TreeSet<>(); if (path.startsWith("file:") && path.contains("!")) { @@ -428,6 +520,7 @@ public static String uptime(long uptimeMs) { numSeconds); } + @Deprecated private static void printHelp() { print("Usage: solr COMMAND OPTIONS"); diff --git a/solr/core/src/java/org/apache/solr/cli/StartCommand.java b/solr/core/src/java/org/apache/solr/cli/StartCommand.java new file mode 100644 index 000000000000..b7c0b3f07c5b --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/StartCommand.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import java.util.concurrent.Callable; +import picocli.CommandLine; + +@CommandLine.Command( + name = "start", + description = "Starts Solr in standalone or SolrCloud mode.", + mixinStandardHelpOptions = true) +public class StartCommand implements Callable { + + @CommandLine.Spec CommandLine.Model.CommandSpec spec; + + @CommandLine.Option( + names = {"-f", "--foreground"}, + description = + "Start Solr in foreground; default is background with logs to solr-PORT-console.log") + boolean foreground; + + @CommandLine.Option( + names = "--user-managed", + description = "Start Solr in standalone mode. Default is SolrCloud (ZooKeeper) mode.") + boolean userManaged; + + @CommandLine.Option(names = "--host", description = "Specify the hostname for this Solr instance") + String host; + + @CommandLine.Option( + names = {"-p", "--port"}, + description = + "Specify the Solr HTTP port; default is 8983. STOP_PORT=($SOLR_PORT-1000), RMI_PORT=($SOLR_PORT+10000)") + String port; + + @CommandLine.Option( + names = "--server-dir", + description = "Specify the Solr server directory; default is 'server'") + String serverDir; + + @CommandLine.Option( + names = {"-z", "--zk-host"}, + description = + "Zookeeper connection string; default is to start an embedded ZooKeeper on PORT+10000") + String zkHost; + + @CommandLine.Option( + names = {"-m", "--memory"}, + description = "Set JVM heap size, e.g., -m 4g sets -Xms4g -Xmx4g; default is 512m") + String memory; + + @CommandLine.Option( + names = "--solr-home", + description = + "Set solr.solr.home system property; default is 'server/solr'. Ignored in examples mode") + String solrHome; + + @CommandLine.Option( + names = "--data-home", + description = + "Set solr.data.home system property for index data storage; default is solr.solr.home") + String dataHome; + + @CommandLine.Option( + names = {"-e", "--example"}, + description = "Run an example: cloud, techproducts, schemaless, films") + String example; + + @CommandLine.Option( + names = "--jvm-opts", + description = "Additional JVM parameters, e.g., --jvm-opts \"-verbose:gc\"") + String jvmOpts; + + @CommandLine.Option( + names = {"-j", "--jettyconfig"}, + description = + "Additional Jetty parameters, e.g., -j \"--include-jetty-dir=/etc/jetty/custom/server/\"") + String jettyParams; + + @CommandLine.Option( + names = {"-y", "--no-prompt"}, + description = "Don't prompt for input; accept all defaults when running examples") + boolean noPrompt; + + @CommandLine.Option( + names = "--prompt-inputs", + description = + "Don't prompt for input; comma-delimited list of inputs to use when running examples that accept user input", + paramLabel = "") + String promptInputs; + + @CommandLine.Option( + names = "--example-dir", + description = + "Override the directory containing example configurations used when running examples with --example") + String exampleDir; + + @CommandLine.Option( + names = "--force", + description = "Override warning when attempting to start Solr as root user") + boolean force; + + @CommandLine.Option( + names = {"--verbose"}, + description = "Set log level to DEBUG (verbose); default is INFO") + boolean verbose; + + @CommandLine.Option( + names = {"--quiet", "-q"}, + description = "Set log level to WARN (quiet); default is INFO") + boolean quiet; + + @CommandLine.Option( + names = "--fullhelp", + description = "Print detailed help with full option descriptions", + hidden = true) + boolean fullhelp; + + @Override + public Integer call() { + if (fullhelp) { + printFullHelp(); + } + // Actual start logic is handled by the bin/solr shell script. + return 0; + } + + private void printFullHelp() { + CLIO.out( + """ + Usage: solr start [OPTIONS] + + Starts Solr in standalone or SolrCloud mode. + + Options: + -f, --foreground + Start Solr in foreground; default starts Solr in the background and sends + stdout / stderr to solr-PORT-console.log + + --user-managed + Start Solr in user managed aka standalone mode. + See: https://solr.apache.org/guide/solr/latest/deployment-guide/cluster-types.html + + --host + Specify the hostname for this Solr instance + + -p, --port + Specify the port to start the Solr HTTP listener on; default is 8983. + The specified port (SOLR_PORT) will also be used to determine the stop port: + STOP_PORT=($SOLR_PORT-1000) and JMX RMI listen port RMI_PORT=($SOLR_PORT+10000). + For instance, if you set -p 8985, then STOP_PORT=7985 and RMI_PORT=18985 + + --server-dir + Specify the Solr server directory; defaults to server + + -z, --zk-host + Zookeeper connection string; ignored in User Managed (--user-managed) mode. + If neither ZK_HOST is defined in solr.in.sh nor -z is specified, an embedded + ZooKeeper instance will be launched. + Set ZK_CREATE_CHROOT=true if your ZK host has a chroot path to create it automatically. + + -m, --memory + Sets the min (-Xms) and max (-Xmx) heap size for the JVM, e.g., -m 4g sets + -Xms4g -Xmx4g; by default, this script sets the heap size to 512m + + --solr-home + Sets the solr.solr.home system property; Solr will create core directories here. + Allows running multiple Solr instances on the same host while reusing the same + server directory. If set, the directory should contain a solr.xml file unless + solr.xml exists in ZooKeeper. Ignored when running examples (-e). + Default: server/solr + + --data-home + Sets the solr.data.home system property, where Solr stores index data in + /data subdirectories. If not set, Solr uses solr.solr.home. + + -e, --example + Name of the example to run; available examples: + cloud: SolrCloud example + techproducts: Comprehensive example illustrating many of Solr's core capabilities + schemaless: Schema-less example (schema inferred from data during indexing) + films: Example starting with _default configset with explicit fields + + --jvm-opts + Additional parameters to pass to the JVM when starting Solr, e.g., to enable + a Java debugger: --jvm-opts "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=18983" + Wrap additional parameters in double quotes. + + -j, --jettyconfig + Additional parameters to pass to Jetty, e.g., to add a config folder: + -j "--include-jetty-dir=/etc/jetty/custom/server/" + Wrap additional parameters in double quotes. + + -y, --no-prompt + Don't prompt for input; accept all defaults when running examples. + + --prompt-inputs + Don't prompt for input; comma-delimited list of inputs for examples that + accept user input. + + --example-dir + Override the directory containing example configurations. + + --force + Override warning when attempting to start Solr as the root user. + + --verbose + Set log level to DEBUG (verbose); default is INFO + + -q, --quiet + Set log level to WARN (quiet); default is INFO + """); + } +} diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java b/solr/core/src/java/org/apache/solr/cli/StatusTool.java index ca9391fca18b..bbe253a6887f 100644 --- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java @@ -44,8 +44,39 @@ * *

Get the status of a Solr server. */ +@picocli.CommandLine.Command( + name = "status", + mixinStandardHelpOptions = true, + description = "Get the status of a Solr server.") public class StatusTool extends ToolBase { + @picocli.CommandLine.Option( + names = {"--max-wait-secs"}, + description = "Wait up to the specified number of seconds to see Solr running.") + private Integer maxWaitSecs; + @picocli.CommandLine.Option( + names = {"-p", "--port"}, + description = "Port on localhost to check status for") + private Integer port; + + @picocli.CommandLine.Option( + names = {"-s", "--solr-url"}, + description = "Base Solr URL, which can be used to determine the zk-host if that's not known") + private String solrUrl; + + @picocli.CommandLine.Option( + names = {"--short"}, + paramLabel = "short", + description = "Short format. Prints one URL per line for running instances") + private boolean shortFormat; + + @picocli.CommandLine.Option( + names = {"-u", "--credentials"}, + description = + "Credentials in the format username:password. Example: --credentials solr:SolrRocks") + private String credentials; + + @Deprecated private static final Option MAX_WAIT_SECS_OPTION = Option.builder() .longOpt("max-wait-secs") @@ -56,6 +87,7 @@ public class StatusTool extends ToolBase { .desc("Wait up to the specified number of seconds to see Solr running.") .get(); + @Deprecated public static final Option PORT_OPTION = Option.builder("p") .longOpt("port") @@ -65,6 +97,7 @@ public class StatusTool extends ToolBase { .desc("Port on localhost to check status for") .get(); + @Deprecated public static final Option SHORT_OPTION = Option.builder() .longOpt("short") @@ -74,6 +107,10 @@ public class StatusTool extends ToolBase { private final SolrProcessManager processMgr; + public StatusTool() { + this(new DefaultToolRuntime()); + } + public StatusTool(ToolRuntime runtime) { super(runtime); processMgr = new SolrProcessManager(); @@ -98,11 +135,15 @@ public Options getOptions() { @Override public void runImpl(CommandLine cli) throws Exception { - String solrUrl = cli.getOptionValue(CommonCLIOptions.SOLR_URL_OPTION); - Integer port = cli.hasOption(PORT_OPTION) ? cli.getParsedOptionValue(PORT_OPTION) : null; - boolean shortFormat = cli.hasOption(SHORT_OPTION); - int maxWaitSecs = cli.getParsedOptionValue(MAX_WAIT_SECS_OPTION, 0); + solrUrl = cli.getOptionValue(CommonCLIOptions.SOLR_URL_OPTION); + port = cli.hasOption(PORT_OPTION) ? cli.getParsedOptionValue(PORT_OPTION) : null; + shortFormat = cli.hasOption(SHORT_OPTION); + maxWaitSecs = cli.getParsedOptionValue(MAX_WAIT_SECS_OPTION, 0); + + runTool(); + } + public int runTool() throws Exception { if (solrUrl != null) { if (!URLUtil.hasScheme(solrUrl)) { CLIO.err("Invalid URL provided: " + solrUrl); @@ -113,14 +154,14 @@ public void runImpl(CommandLine cli) throws Exception { if (maxWaitSecs > 0) { // Used by Windows start script when starting Solr try { - waitForSolrUpAndPrintStatus(solrUrl, cli, maxWaitSecs); + waitForSolrUpAndPrintStatus(solrUrl); runtime.exit(0); } catch (Exception e) { CLIO.err(e.getMessage()); runtime.exit(1); } } else { - boolean running = printStatusFromRunningSolr(solrUrl, cli); + boolean running = printStatusFromRunningSolr(solrUrl); runtime.exit(running ? 0 : 1); } } @@ -135,7 +176,7 @@ public void runImpl(CommandLine cli) throws Exception { if (shortFormat) { CLIO.out(solrUrl); } else { - printProcessStatus(proc.get(), cli); + printProcessStatus(proc.get()); } runtime.exit(0); } @@ -148,7 +189,7 @@ public void runImpl(CommandLine cli) throws Exception { if (shortFormat) { CLIO.out(process.getLocalUrl()); } else { - printProcessStatus(process, cli); + printProcessStatus(process); } } } else { @@ -156,17 +197,16 @@ public void runImpl(CommandLine cli) throws Exception { CLIO.out("\nNo Solr nodes are running.\n"); } } + return 0; } - private void printProcessStatus(SolrProcess process, CommandLine cli) throws Exception { - int maxWaitSecs = cli.getParsedOptionValue(MAX_WAIT_SECS_OPTION, 0); - boolean shortFormat = cli.hasOption(SHORT_OPTION); + private void printProcessStatus(SolrProcess process) throws Exception { String pidUrl = process.getLocalUrl(); if (shortFormat) { CLIO.out(pidUrl); } else { if (maxWaitSecs > 0) { - waitForSolrUpAndPrintStatus(pidUrl, cli, maxWaitSecs); + waitForSolrUpAndPrintStatus(pidUrl); } else { CLIO.out( String.format( @@ -174,23 +214,22 @@ private void printProcessStatus(SolrProcess process, CommandLine cli) throws Exc "\nSolr process %s running on port %s", process.pid(), process.port())); - printStatusFromRunningSolr(pidUrl, cli); + printStatusFromRunningSolr(pidUrl); } } CLIO.out(""); } - public void waitForSolrUpAndPrintStatus(String solrUrl, CommandLine cli, int maxWaitSecs) - throws Exception { + public void waitForSolrUpAndPrintStatus(String pidUrl) throws Exception { int solrPort = -1; try { - solrPort = CLIUtils.portFromUrl(solrUrl); + solrPort = CLIUtils.portFromUrl(pidUrl); } catch (Exception e) { CLIO.err("Invalid URL provided, does not contain port"); runtime.exit(1); } echo("Waiting up to " + maxWaitSecs + " seconds to see Solr running on port " + solrPort); - boolean solrUp = waitForSolrUp(solrUrl, cli, maxWaitSecs); + boolean solrUp = waitForSolrUp(pidUrl); if (solrUp) { echo("Started Solr server on port " + solrPort + ". Happy searching!"); } else { @@ -202,35 +241,28 @@ public void waitForSolrUpAndPrintStatus(String solrUrl, CommandLine cli, int max /** * Wait for Solr to come online and return true if it does, false otherwise. * - * @param solrUrl the URL of the Solr server - * @param cli the command line options - * @param maxWaitSecs the maximum number of seconds to wait * @return true if Solr comes online, false otherwise */ - public boolean waitForSolrUp(String solrUrl, CommandLine cli, int maxWaitSecs) throws Exception { + public boolean waitForSolrUp(String pidUrl) throws Exception { try { - waitToSeeSolrUp( - solrUrl, - cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION), - maxWaitSecs, - TimeUnit.SECONDS); + waitToSeeSolrUp(pidUrl, credentials, maxWaitSecs, TimeUnit.SECONDS); return true; } catch (TimeoutException timeout) { return false; } } - public boolean printStatusFromRunningSolr(String solrUrl, CommandLine cli) { + public boolean printStatusFromRunningSolr(String pidUrl) { String statusJson = null; try { - statusJson = statusFromRunningSolr(solrUrl, cli); + statusJson = statusFromRunningSolr(pidUrl); } catch (Exception e) { /* ignore */ } if (statusJson != null) { runtime.println(statusJson); } else { - CLIO.err("Solr at " + solrUrl + " not online."); + CLIO.err("Solr at " + pidUrl + " not online."); } return statusJson != null; } @@ -238,16 +270,13 @@ public boolean printStatusFromRunningSolr(String solrUrl, CommandLine cli) { /** * Get the status of a Solr server and responds with a JSON status string. * - * @param solrUrl the URL of the Solr server - * @param cli the command line options * @return the status of the Solr server or null if the server is not online * @throws Exception if there is an error getting the status */ - public String statusFromRunningSolr(String solrUrl, CommandLine cli) throws Exception { + public String statusFromRunningSolr(String pidUrl) throws Exception { try { CharArr arr = new CharArr(); - new JSONWriter(arr, 2) - .write(getStatus(solrUrl, cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION))); + new JSONWriter(arr, 2).write(getStatus(pidUrl)); return arr.toString(); } catch (Exception exc) { if (CLIUtils.exceptionIsAuthRelated(exc)) { @@ -257,19 +286,23 @@ public String statusFromRunningSolr(String solrUrl, CommandLine cli) throws Exce // this is not actually an error from the tool as it's ok if Solr is not online. return null; } else { - throw new Exception("Failed to get system information from " + solrUrl + " due to: " + exc); + throw new Exception("Failed to get system information from " + pidUrl + " due to: " + exc); } } } + public Map waitToSeeSolrUp(String pidUrl) throws Exception { + return waitToSeeSolrUp(pidUrl, credentials, maxWaitSecs, TimeUnit.SECONDS); + } + @SuppressWarnings("BusyWait") public Map waitToSeeSolrUp( - String solrUrl, String credentials, long maxWait, TimeUnit unit) throws Exception { + String pidUrl, String credentials, long maxWait, TimeUnit unit) throws Exception { long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(maxWait, unit); while (System.nanoTime() < timeout) { try { - return getStatus(solrUrl, credentials); + return getStatus(pidUrl); } catch (Exception exc) { if (CLIUtils.exceptionIsAuthRelated(exc)) { throw exc; @@ -289,8 +322,12 @@ public Map waitToSeeSolrUp( + " seconds!"); } - public Map getStatus(String solrUrl, String credentials) throws Exception { - try (var solrClient = CLIUtils.getSolrClient(solrUrl, credentials)) { + public Map getStatus(String pidUrl) throws Exception { + return getStatus(pidUrl, credentials); + } + + public Map getStatus(String pidUrl, String credentials) throws Exception { + try (var solrClient = CLIUtils.getSolrClient(pidUrl, credentials)) { return reportStatus(solrClient); } } @@ -340,4 +377,9 @@ private static Map getCloudStatus(SolrClient solrClient, String return cloudStatus; } + + @Override + public int callTool() throws Exception { + return runTool(); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/StopCommand.java b/solr/core/src/java/org/apache/solr/cli/StopCommand.java new file mode 100644 index 000000000000..2dd4a762dfe0 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/StopCommand.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import picocli.CommandLine; + +/** + * This class is currently only used for printing CLI usage. + * The stop logic is currently handled in start script. + */ +@CommandLine.Command(name = "stop", description = "Stops Solr.", mixinStandardHelpOptions = true) +public class StopCommand { + + @CommandLine.Option( + names = {"-p", "--port"}, + description = + "Specify the port the Solr HTTP listener is bound to.\n" + + "The STOP_PORT is derived as ($SOLR_PORT-1000).") + String port; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "Stop key; default is solrrocks", + defaultValue = "solrrocks") + String key; + + @CommandLine.Option( + names = "--all", + description = "Find and stop all running Solr servers on this host") + boolean all; + + @CommandLine.Option( + names = {"--verbose"}, + description = "Enable verbose mode.") + boolean verbose; +} diff --git a/solr/core/src/java/org/apache/solr/cli/StreamTool.java b/solr/core/src/java/org/apache/solr/cli/StreamTool.java index 61d95c79d418..a0830d9919a4 100644 --- a/solr/core/src/java/org/apache/solr/cli/StreamTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StreamTool.java @@ -480,6 +480,11 @@ static String listToString(List values, String internalDelim) { return buf.toString(); } + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } + static String readExpression(LineNumberReader bufferedReader, String[] args) throws IOException { StringBuilder exprBuff = new StringBuilder(); diff --git a/solr/core/src/java/org/apache/solr/cli/Tool.java b/solr/core/src/java/org/apache/solr/cli/Tool.java index 1ad58bce50e7..acaf9c62d31b 100644 --- a/solr/core/src/java/org/apache/solr/cli/Tool.java +++ b/solr/core/src/java/org/apache/solr/cli/Tool.java @@ -22,6 +22,7 @@ public interface Tool { /** Defines the interface to a Solr tool that can be run from this command-line app. */ + @Deprecated String getName(); /** @@ -30,6 +31,7 @@ public interface Tool { * * @return The custom usage string or 'null' to auto generate (default) */ + @Deprecated default String getUsage() { return null; } @@ -37,6 +39,7 @@ default String getUsage() { /** * Optional header to display before the options in help output. Defaults to 'List of options:' */ + @Deprecated default String getHeader() { return "List of options:"; } @@ -45,6 +48,7 @@ default String getHeader() { * Optional footer to display after the options in help output. Defaults to a link to reference * guide */ + @Deprecated default String getFooter() { return "\nPlease see the Reference Guide for more tools documentation: https://solr.apache.org/guide/solr/latest/deployment-guide/solr-control-script-reference.html"; } @@ -57,7 +61,9 @@ default String getFooter() { * * @return The {@link Options} this tool supports. */ + @Deprecated Options getOptions(); + @Deprecated int runTool(CommandLine cli) throws Exception; } diff --git a/solr/core/src/java/org/apache/solr/cli/ToolBase.java b/solr/core/src/java/org/apache/solr/cli/ToolBase.java index 9d3734c3d931..8b3d4304cf3c 100644 --- a/solr/core/src/java/org/apache/solr/cli/ToolBase.java +++ b/solr/core/src/java/org/apache/solr/cli/ToolBase.java @@ -18,18 +18,21 @@ package org.apache.solr.cli; import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.concurrent.Callable; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.Options; import org.apache.solr.client.solrj.request.json.JacksonContentWriter; import org.apache.solr.util.StartupLoggingUtils; -public abstract class ToolBase implements Tool { +public abstract class ToolBase implements Tool, Callable { + @picocli.CommandLine.Option( + names = {"-v", "--verbose"}, + description = "Enable verbose mode.") + private boolean verbose = false; protected final ToolRuntime runtime; - private boolean verbose = false; - protected ToolBase(ToolRuntime runtime) { this.runtime = runtime; } @@ -75,6 +78,7 @@ public ToolRuntime getRuntime() { * * @return OptionGroup validates that only one option is supplied by the caller. */ + @Deprecated public OptionGroup getConnectionOptions() { OptionGroup optionGroup = new OptionGroup(); optionGroup.addOption(CommonCLIOptions.SOLR_URL_OPTION); @@ -112,5 +116,36 @@ private void raiseLogLevelUnlessVerbose() { } } + @Deprecated public abstract void runImpl(CommandLine cli) throws Exception; + + /** + * Called by picocli to execute the tool's logic. Each tool must implement this method to support + * the picocli-based invocation path. + */ + public abstract int callTool() throws Exception; + + /** Called by picocli for a tool invocation. Delegates to {@link #callTool()}. */ + @Override + public Integer call() { + raiseLogLevelUnlessVerbose(); + + int toolExitStatus = 0; + try { + toolExitStatus = callTool(); + } catch (Exception exc) { + // since this is a CLI, spare the user the stacktrace + String excMsg = exc.getMessage(); + if (excMsg != null) { + CLIO.err("\nERROR: " + excMsg + "\n"); + if (verbose) { + exc.printStackTrace(CLIO.getErrStream()); + } + toolExitStatus = 1; + } else { + throw new RuntimeException(exc); + } + } + return toolExitStatus; + } } diff --git a/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java b/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java index 021f05d7e82c..0639a9ce9969 100644 --- a/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java +++ b/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java @@ -71,4 +71,9 @@ public void runImpl(CommandLine cli) throws Exception { zkClient.updateACLs(path); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/VersionTool.java b/solr/core/src/java/org/apache/solr/cli/VersionTool.java index b8ae826ebc6a..f8f8393dbde5 100644 --- a/solr/core/src/java/org/apache/solr/cli/VersionTool.java +++ b/solr/core/src/java/org/apache/solr/cli/VersionTool.java @@ -20,8 +20,13 @@ import org.apache.commons.cli.CommandLine; import org.apache.solr.client.api.util.SolrVersion; +@picocli.CommandLine.Command(name = "version", description = "Prints the Solr version.") public class VersionTool extends ToolBase { + public VersionTool() { + this(new DefaultToolRuntime()); + } + public VersionTool(ToolRuntime runtime) { super(runtime); } @@ -33,6 +38,16 @@ public String getName() { @Override public void runImpl(CommandLine cli) throws Exception { + printVersion(); + } + + @Override + public int callTool() throws Exception { + printVersion(); + return 0; + } + + private void printVersion() { CLIO.out("Solr version is: " + SolrVersion.LATEST); } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkConnectionOptions.java b/solr/core/src/java/org/apache/solr/cli/ZkConnectionOptions.java new file mode 100644 index 000000000000..432d8cddd03e --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/ZkConnectionOptions.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import java.util.Map; +import org.apache.solr.client.solrj.SolrClient; +import picocli.CommandLine; + +/** + * Picocli mixin providing common ZooKeeper connection options shared across ZK sub-commands. + * + *

Use {@code @CommandLine.Mixin ZkConnectionOptions zkOpts} in a command class to inherit these + * options. Call {@link #resolveZkHost()} to obtain a resolved ZooKeeper connection string, applying + * the same fallback logic as the commons-cli path. + */ +public class ZkConnectionOptions { + + @CommandLine.Option( + names = {"-z", "--zk-host"}, + description = + "Zookeeper connection string; unnecessary if ZK_HOST is defined in solr.in.sh; otherwise, defaults to " + + CommonCLIOptions.DefaultValues.ZK_HOST + + '.') + public String zkHost; + + @CommandLine.Option( + names = {"-s", "--solr-url"}, + description = + "Base Solr URL, which can be used to determine the zk-host if --zk-host is not known") + public String solrUrl; + + @CommandLine.Option( + names = {"-u", "--credentials"}, + description = + "Credentials in the format username:password. Example: --credentials solr:SolrRocks") + public String credentials; + + /** + * Resolves the ZooKeeper connection string using the following precedence: + * + *

    + *
  1. Explicit {@code --zk-host} option value + *
  2. ZooKeeper host derived by querying the Solr instance at {@code --solr-url} + *
  3. ZooKeeper host derived by querying the default Solr URL ({@code http://localhost:8983}), + * with a warning printed to stderr + *
+ * + * @return resolved ZooKeeper connection string, never null + * @throws IllegalStateException if the Solr instance is not running in SolrCloud mode + * @throws Exception if the Solr instance cannot be reached + */ + public String resolveZkHost() throws Exception { + if (zkHost != null) { + return zkHost; + } + + String resolvedSolrUrl = solrUrl; + if (resolvedSolrUrl == null) { + resolvedSolrUrl = CLIUtils.getDefaultSolrUrl(); + CLIO.err( + "Neither --zk-host or --solr-url parameters, nor ZK_HOST env var provided, so assuming solr url is " + + resolvedSolrUrl + + "."); + } + + try (SolrClient solrClient = CLIUtils.getSolrClient(resolvedSolrUrl, credentials)) { + Map status = StatusTool.reportStatus(solrClient); + @SuppressWarnings("unchecked") + Map cloud = (Map) status.get("cloud"); + if (cloud != null) { + String zookeeper = (String) cloud.get("ZooKeeper"); + if (zookeeper != null && zookeeper.endsWith("(embedded)")) { + zookeeper = zookeeper.substring(0, zookeeper.length() - "(embedded)".length()); + } + if (zookeeper != null) { + return zookeeper; + } + } + } + + throw new IllegalStateException( + "Solr at " + + resolvedSolrUrl + + " is not running in SolrCloud mode. Cannot use zk commands."); + } +} diff --git a/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java b/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java index 4bf9a2663156..3e7f912f2485 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java @@ -207,4 +207,9 @@ public void runImpl(CommandLine cli) throws Exception { throw (e); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java b/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java index 2ca4be286f20..cd09844e05c1 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java @@ -17,16 +17,39 @@ package org.apache.solr.cli; import java.lang.invoke.MethodHandles; +import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; +import org.apache.solr.client.solrj.impl.SolrZkClientTimeout; import org.apache.solr.common.cloud.SolrZkClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Supports zk ls command in the bin/solr script. */ +@picocli.CommandLine.Command( + name = "ls", + mixinStandardHelpOptions = true, + description = "List the contents of a ZooKeeper node.") public class ZkLsTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + + @picocli.CommandLine.Parameters( + index = "0", + arity = "1", + description = "The path of the ZooKeeper znode path to list.") + private String path; + + @picocli.CommandLine.Option( + names = {"-r", "--recursive"}, + description = "Apply the command recursively.") + private boolean recursive; + + public ZkLsTool() { + this(new DefaultToolRuntime()); + } + public ZkLsTool(ToolRuntime runtime) { super(runtime); } @@ -57,7 +80,6 @@ public void runImpl(CommandLine cli) throws Exception { try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) { echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); - boolean recursive = cli.hasOption(CommonCLIOptions.RECURSIVE_OPTION); echoIfVerbose( "Getting listing for ZooKeeper node " @@ -72,4 +94,33 @@ public void runImpl(CommandLine cli) throws Exception { throw (e); } } + + private void doLs(SolrZkClient zkClient) throws Exception { + echoIfVerbose("\nConnecting to ZooKeeper at " + zkOpts.zkHost + " ..."); + echoIfVerbose( + "Getting listing for ZooKeeper node " + + path + + " from ZooKeeper at " + + zkOpts.zkHost + + " recursive: " + + recursive); + runtime.print(zkClient.listZnode(path, recursive)); + } + + @Override + public int callTool() throws Exception { + String zkHost = zkOpts.resolveZkHost(); + + try (SolrZkClient zkClient = + new SolrZkClient.Builder() + .withUrl(zkHost) + .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) + .build()) { + doLs(zkClient); + return 0; + } catch (Exception e) { + log.error("Could not complete ls operation for reason: ", e); + throw (e); + } + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java b/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java index 2a644f2a1364..5c0d51a30c19 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java @@ -85,4 +85,9 @@ public void runImpl(CommandLine cli) throws Exception { throw (e); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java b/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java index a11ac0884f87..759eacb1b7b9 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java @@ -98,4 +98,9 @@ public void runImpl(CommandLine cli) throws Exception { throw (e); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java b/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java index 92e6b4daaa4e..8aab732c6f8b 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java @@ -84,4 +84,9 @@ public void runImpl(CommandLine cli) throws Exception { throw (e); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkTool.java b/solr/core/src/java/org/apache/solr/cli/ZkTool.java new file mode 100644 index 000000000000..af69c2461e25 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/ZkTool.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import java.util.concurrent.Callable; +import picocli.CommandLine; + +/** + * Sub commands for working with ZooKeeper, only here to provide a common parent for the subcommands + * and print tool help. + */ +@CommandLine.Command( + name = "zk", + mixinStandardHelpOptions = true, + description = "Sub commands for working with ZooKeeper.", + subcommands = {ZkLsTool.class}) +public class ZkTool implements Callable { + + @CommandLine.Spec CommandLine.Model.CommandSpec spec; + + @Override + public Integer call() { + spec.commandLine().usage(CLIO.getOutStream()); + return 0; + } +} diff --git a/solr/core/src/java/org/apache/solr/cli/ZkToolHelp.java b/solr/core/src/java/org/apache/solr/cli/ZkToolHelp.java index 9251484a859f..8217702f511f 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkToolHelp.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkToolHelp.java @@ -77,4 +77,9 @@ public void runImpl(CommandLine cli) throws Exception { "Pass --help or -h after any COMMAND to see command-specific usage information such as: ./solr zk ls --help"); } } + + @Override + public int callTool() throws Exception { + throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + } } diff --git a/solr/test-framework/build.gradle b/solr/test-framework/build.gradle index a7bac2b63bd8..78d2ed3592fe 100644 --- a/solr/test-framework/build.gradle +++ b/solr/test-framework/build.gradle @@ -67,6 +67,10 @@ dependencies { implementation libs.dropwizard.metrics.core implementation libs.dropwizard.metrics.jetty12.ee10 implementation libs.commonscli.commonscli + permitUnusedDeclared libs.commonscli.commonscli + implementation libs.picocli + permitUnusedDeclared libs.picocli + annotationProcessor libs.picocli.codegen implementation libs.apache.httpcomponents.httpclient implementation libs.apache.httpcomponents.httpcore implementation libs.opentelemetry.api diff --git a/solr/test-framework/gradle.lockfile b/solr/test-framework/gradle.lockfile index b97f50c3595d..6ff709116263 100644 --- a/solr/test-framework/gradle.lockfile +++ b/solr/test-framework/gradle.lockfile @@ -31,10 +31,11 @@ com.google.protobuf:protobuf-java:3.25.8=annotationProcessor,errorprone,testAnno com.j256.simplemagic:simplemagic:1.17=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath com.jayway.jsonpath:json-path:2.9.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath com.tdunning:t-digest:3.3=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath -commons-cli:commons-cli:1.10.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-cli:commons-cli:1.10.0=apiHelper,compileClasspath,jarValidation,permitUnusedDeclared,runtimeClasspath,testCompileClasspath,testRuntimeClasspath commons-codec:commons-codec:1.19.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.20.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -info.picocli:picocli:4.7.6=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath +info.picocli:picocli-codegen:4.7.6=annotationProcessor +info.picocli:picocli:4.7.6=annotationProcessor,apiHelper,compileClasspath,jarValidation,permitUnusedDeclared,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-annotation:4.2.26=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-core:4.2.26=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.dropwizard.metrics:metrics-jetty12-ee10:4.2.26=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath From 8c23c9fb578d677732d90d5af921a49e13a77114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Tue, 7 Apr 2026 01:19:30 +0200 Subject: [PATCH 5/7] SOLR-17697 Remove eager ZK connection from CliDefaultValueProvider (#4268) --- .../solr/cli/CliDefaultValueProvider.java | 46 +------------------ .../java/org/apache/solr/cli/StopCommand.java | 4 +- 2 files changed, 3 insertions(+), 47 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/cli/CliDefaultValueProvider.java b/solr/core/src/java/org/apache/solr/cli/CliDefaultValueProvider.java index 99cb2ab95564..9362e070d817 100644 --- a/solr/core/src/java/org/apache/solr/cli/CliDefaultValueProvider.java +++ b/solr/core/src/java/org/apache/solr/cli/CliDefaultValueProvider.java @@ -16,12 +16,6 @@ */ package org.apache.solr.cli; -import static org.apache.solr.cli.CLIUtils.getCloudSolrClient; -import static org.apache.solr.cli.CLIUtils.normalizeSolrUrl; - -import java.util.Set; -import org.apache.solr.client.solrj.impl.CloudSolrClient; -import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.util.EnvUtils; import picocli.CommandLine; @@ -31,48 +25,10 @@ public class CliDefaultValueProvider implements CommandLine.IDefaultValueProvide public String defaultValue(CommandLine.Model.ArgSpec argSpec) throws Exception { return switch (argSpec.paramLabel()) { case "" -> EnvUtils.getProperty("zkHost"); - case "" -> { - String val = EnvUtils.getProperty("solr.url"); - yield val != null ? val : resolveSolrUrlViaZkHost(argSpec); - } + case "" -> EnvUtils.getProperty("solr.url"); case "" -> EnvUtils.getProperty("solr.port", "8983"); case "" -> EnvUtils.getProperty("solr.max.wait.seconds", "0"); default -> null; }; } - - /** - * If no solrUrl is provided on the command line, and SOLR_URL is not set, this method will be - * used to determine the solrUrl from the zkHost. - * - * @param argSpec the argSpec for the solrUrl option - * @return the solrUrl - * @throws Exception if an error occurs - */ - public static String resolveSolrUrlViaZkHost(picocli.CommandLine.Model.ArgSpec argSpec) - throws Exception { - // Find value of zkHost from command line options. The argSpec passed in will be for the - // solrUrl option. - CommandLine.Model.OptionSpec zkHostOption = argSpec.command().findOption("--zk-host"); - - String zkHost = zkHostOption != null ? zkHostOption.getValue() : null; - if (zkHost == null) { - return null; - } - - String solrUrl; - try (CloudSolrClient cloudSolrClient = getCloudSolrClient(zkHost)) { - cloudSolrClient.connect(); - Set liveNodes = cloudSolrClient.getClusterState().getLiveNodes(); - if (liveNodes.isEmpty()) - throw new IllegalStateException( - "No live nodes found! Cannot determine 'solrUrl' from ZooKeeper: " + zkHost); - - String firstLiveNode = liveNodes.iterator().next(); - solrUrl = ZkStateReader.from(cloudSolrClient).getBaseUrlForNodeName(firstLiveNode); - solrUrl = normalizeSolrUrl(solrUrl, false); - } - solrUrl = normalizeSolrUrl(solrUrl); - return solrUrl; - } } diff --git a/solr/core/src/java/org/apache/solr/cli/StopCommand.java b/solr/core/src/java/org/apache/solr/cli/StopCommand.java index 2dd4a762dfe0..f9f3fc626280 100644 --- a/solr/core/src/java/org/apache/solr/cli/StopCommand.java +++ b/solr/core/src/java/org/apache/solr/cli/StopCommand.java @@ -19,8 +19,8 @@ import picocli.CommandLine; /** - * This class is currently only used for printing CLI usage. - * The stop logic is currently handled in start script. + * This class is currently only used for printing CLI usage. The stop logic is currently handled in + * start script. */ @CommandLine.Command(name = "stop", description = "Stops Solr.", mixinStandardHelpOptions = true) public class StopCommand { From bae63d4b1d647ec0dde19588373d87e9113d5b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Tue, 7 Apr 2026 16:25:48 +0200 Subject: [PATCH 6/7] SOLR-17697 Enable picocli for remaining zk sub commands (#4267) --- .../solr/cli/ConfigSetDownloadTool.java | 67 ++++++--- .../org/apache/solr/cli/ConfigSetOptions.java | 41 ++++++ .../apache/solr/cli/ConfigSetUploadTool.java | 73 +++++++--- .../org/apache/solr/cli/RecursiveOption.java | 31 +++++ .../src/java/org/apache/solr/cli/SolrCLI.java | 15 +- .../org/apache/solr/cli/UpdateACLTool.java | 40 +++++- .../java/org/apache/solr/cli/ZkCpTool.java | 51 ++++++- .../java/org/apache/solr/cli/ZkLsTool.java | 40 ++---- .../org/apache/solr/cli/ZkMkrootTool.java | 53 +++++++- .../java/org/apache/solr/cli/ZkMvTool.java | 89 +++++++++--- .../java/org/apache/solr/cli/ZkRmTool.java | 74 +++++++--- .../src/java/org/apache/solr/cli/ZkTool.java | 12 +- .../solr/cli/ZkSubcommandsPicocliTest.java | 50 +++++++ .../apache/solr/cli/ZkSubcommandsTest.java | 128 +++++++++++++++--- solr/packaging/build.gradle | 2 + solr/packaging/test/test_zk.bats | 15 +- 16 files changed, 629 insertions(+), 152 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/cli/ConfigSetOptions.java create mode 100644 solr/core/src/java/org/apache/solr/cli/RecursiveOption.java create mode 100644 solr/core/src/test/org/apache/solr/cli/ZkSubcommandsPicocliTest.java diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java b/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java index a20c5bff853f..aa5b3c91fac6 100644 --- a/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java @@ -19,14 +19,20 @@ import java.lang.invoke.MethodHandles; import java.nio.file.Files; import java.nio.file.Path; +import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.solr.client.solrj.impl.SolrZkClientTimeout; import org.apache.solr.common.cloud.SolrZkClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Supports zk downconfig command in the bin/solr script. */ +@picocli.CommandLine.Command( + name = "downconfig", + mixinStandardHelpOptions = true, + description = "Download a configset from ZooKeeper to the local filesystem.") public class ConfigSetDownloadTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -48,6 +54,14 @@ public class ConfigSetDownloadTool extends ToolBase { .desc("Local directory with configs.") .get(); + @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + + @picocli.CommandLine.Mixin ConfigSetOptions configSetOpts; + + public ConfigSetDownloadTool() { + this(new DefaultToolRuntime()); + } + public ConfigSetDownloadTool(ToolRuntime runtime) { super(runtime); } @@ -74,36 +88,53 @@ public String getUsage() { @Override public void runImpl(CommandLine cli) throws Exception { String zkHost = CLIUtils.getZkHost(cli); - String confName = cli.getOptionValue(CONF_NAME_OPTION); String confDir = cli.getOptionValue(CONF_DIR_OPTION); echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) { - Path configSetPath = Path.of(confDir); - // we try to be nice about having the "conf" in the directory, and we create it if it's not - // there. - if (!configSetPath.endsWith("/conf")) { - configSetPath = configSetPath.resolve("conf"); - } - Files.createDirectories(configSetPath); - echo( - "Downloading configset " - + confName - + " from ZooKeeper at " - + zkHost - + " to directory " - + configSetPath.toAbsolutePath()); - - zkClient.downConfig(confName, configSetPath); + doDownconfig(zkClient, zkHost, confName, confDir); } catch (Exception e) { log.error("Could not complete downconfig operation for reason: ", e); throw (e); } } + private void doDownconfig(SolrZkClient zkClient, String zkHost, String confName, String confDir) + throws Exception { + Path configSetPath = Path.of(confDir); + // we try to be nice about having the "conf" in the directory, and we create it if it's not + // there. + if (!configSetPath.endsWith("conf")) { + configSetPath = configSetPath.resolve("conf"); + } + Files.createDirectories(configSetPath); + echo( + "Downloading configset " + + confName + + " from ZooKeeper at " + + zkHost + + " to directory " + + configSetPath.toAbsolutePath()); + + zkClient.downConfig(confName, configSetPath); + } + @Override public int callTool() throws Exception { - throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + String zkHost = zkOpts.resolveZkHost(); + + echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); + try (SolrZkClient zkClient = + new SolrZkClient.Builder() + .withUrl(zkHost) + .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) + .build()) { + doDownconfig(zkClient, zkHost, configSetOpts.confName, configSetOpts.confDir); + return 0; + } catch (Exception e) { + log.error("Could not complete downconfig operation for reason: ", e); + throw (e); + } } } diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetOptions.java b/solr/core/src/java/org/apache/solr/cli/ConfigSetOptions.java new file mode 100644 index 000000000000..61eb657ff776 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetOptions.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import picocli.CommandLine; + +/** + * Picocli mixin providing common configset options shared by upconfig and downconfig sub-commands. + * + *

Use {@code @CommandLine.Mixin ConfigSetOptions configSetOpts} in a command class to inherit + * these options. + */ +public class ConfigSetOptions { + + @CommandLine.Option( + names = {"-n", "--conf-name"}, + description = "Configset name in ZooKeeper.", + required = true) + public String confName; + + @CommandLine.Option( + names = {"-d", "--conf-dir"}, + description = "Local directory with configs.", + required = true, + paramLabel = "DIR") + public String confDir; +} diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java b/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java index ba3f188b0b9a..87bfd2fb87a4 100644 --- a/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java @@ -18,9 +18,11 @@ import java.lang.invoke.MethodHandles; import java.nio.file.Path; +import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.solr.client.solrj.impl.SolrZkClientTimeout; import org.apache.solr.common.cloud.SolrZkClient; import org.apache.solr.common.cloud.ZkMaintenanceUtils; import org.apache.solr.core.ConfigSetService; @@ -29,6 +31,10 @@ import org.slf4j.LoggerFactory; /** Supports zk upconfig command in the bin/solr script. */ +@picocli.CommandLine.Command( + name = "upconfig", + mixinStandardHelpOptions = true, + description = "Upload a configset from the local filesystem to ZooKeeper.") public class ConfigSetUploadTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -50,6 +56,14 @@ public class ConfigSetUploadTool extends ToolBase { .desc("Local directory with configs.") .get(); + @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + + @picocli.CommandLine.Mixin ConfigSetOptions configSetOpts; + + public ConfigSetUploadTool() { + this(new DefaultToolRuntime()); + } + public ConfigSetUploadTool(ToolRuntime runtime) { super(runtime); } @@ -76,40 +90,55 @@ public String getUsage() { @Override public void runImpl(CommandLine cli) throws Exception { String zkHost = CLIUtils.getZkHost(cli); - - final String solrInstallDir = System.getProperty("solr.install.dir"); - Path solrInstallDirPath = Path.of(solrInstallDir); - String confName = cli.getOptionValue(CONF_NAME_OPTION); String confDir = cli.getOptionValue(CONF_DIR_OPTION); echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) { - final Path configsetsDirPath = CLIUtils.getConfigSetsDir(solrInstallDirPath); - Path confPath = ConfigSetService.getConfigsetPath(confDir, configsetsDirPath.toString()); - - echo( - "Uploading " - + confPath.toAbsolutePath() - + " for config " - + confName - + " to ZooKeeper at " - + zkHost); - FileTypeMagicUtil.assertConfigSetFolderLegal(confPath); - ZkMaintenanceUtils.uploadToZK( - zkClient, - confPath, - ZkMaintenanceUtils.CONFIGS_ZKNODE + "/" + confName, - ZkMaintenanceUtils.UPLOAD_FILENAME_EXCLUDE_PATTERN); - + doUpconfig(zkClient, zkHost, confName, confDir); } catch (Exception e) { log.error("Could not complete upconfig operation for reason: ", e); throw (e); } } + private void doUpconfig(SolrZkClient zkClient, String zkHost, String confName, String confDir) + throws Exception { + final String solrInstallDir = System.getProperty("solr.install.dir"); + Path solrInstallDirPath = Path.of(solrInstallDir); + final Path configsetsDirPath = CLIUtils.getConfigSetsDir(solrInstallDirPath); + Path confPath = ConfigSetService.getConfigsetPath(confDir, configsetsDirPath.toString()); + + echo( + "Uploading " + + confPath.toAbsolutePath() + + " for config " + + confName + + " to ZooKeeper at " + + zkHost); + FileTypeMagicUtil.assertConfigSetFolderLegal(confPath); + ZkMaintenanceUtils.uploadToZK( + zkClient, + confPath, + ZkMaintenanceUtils.CONFIGS_ZKNODE + "/" + confName, + ZkMaintenanceUtils.UPLOAD_FILENAME_EXCLUDE_PATTERN); + } + @Override public int callTool() throws Exception { - throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + String zkHost = zkOpts.resolveZkHost(); + + echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); + try (SolrZkClient zkClient = + new SolrZkClient.Builder() + .withUrl(zkHost) + .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) + .build()) { + doUpconfig(zkClient, zkHost, configSetOpts.confName, configSetOpts.confDir); + return 0; + } catch (Exception e) { + log.error("Could not complete upconfig operation for reason: ", e); + throw (e); + } } } diff --git a/solr/core/src/java/org/apache/solr/cli/RecursiveOption.java b/solr/core/src/java/org/apache/solr/cli/RecursiveOption.java new file mode 100644 index 000000000000..d6f9e3590b3a --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/RecursiveOption.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import picocli.CommandLine; + +/** + * Picocli mixin providing the {@code --recursive} option shared by ZooKeeper sub-commands that + * support recursive traversal (cp, ls, rm). + */ +public class RecursiveOption { + + @CommandLine.Option( + names = {"-r", "--recursive"}, + description = "Apply the command recursively.") + public boolean recursive; +} diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java index 6a6828161ff9..12e1ce518cb8 100755 --- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java +++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java @@ -55,9 +55,10 @@ /** Command-line utility for working with Solr. */ @picocli.CommandLine.Command( - name = "solr", + name = "bin/solr", version = "Apache Solr version " + SolrVersion.LATEST_STRING, mixinStandardHelpOptions = true, + synopsisHeading = "usage: ", commandListHeading = "\nCommands:\n", descriptionHeading = "Global options:\n", footer = { @@ -66,7 +67,7 @@ "", " ./solr start", "", - "Get help for a command by running 'solr COMMAND --help'.", + "Get help for a command by running 'bin/solr COMMAND --help'.", "", "For more help on how to use Solr, head to https://solr.apache.org/" }, @@ -125,8 +126,14 @@ private static void propagateCommandSettings(picocli.CommandLine cmd) { subcommand .getCommandSpec() .usageMessage() - .footer( - "\nFor a full CLI reference, see https://solr.apache.org/guide/solr/latest/deployment-guide/solr-control-script-reference.html"); + .synopsisHeading(cmd.getCommandSpec().usageMessage().synopsisHeading()); + if (subcommand.getSubcommands().isEmpty()) { + subcommand + .getCommandSpec() + .usageMessage() + .footer( + "\nFor a full CLI reference, see https://solr.apache.org/guide/solr/latest/deployment-guide/solr-control-script-reference.html"); + } propagateCommandSettings(subcommand); } } diff --git a/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java b/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java index 0639a9ce9969..cb79d0bf42c2 100644 --- a/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java +++ b/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java @@ -29,9 +29,25 @@ * *

Set ACL properties by directly manipulating ZooKeeper. */ +@picocli.CommandLine.Command( + name = "updateacls", + mixinStandardHelpOptions = true, + description = "Update ACLs for a ZooKeeper znode.") public class UpdateACLTool extends ToolBase { // It is a shame this tool doesn't more closely mimic how the ConfigTool works. + @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + + @picocli.CommandLine.Parameters( + index = "0", + arity = "1", + description = "The ZooKeeper znode path to update ACLs for.") + private String path; + + public UpdateACLTool() { + this(new DefaultToolRuntime()); + } + public UpdateACLTool(ToolRuntime runtime) { super(runtime); } @@ -53,7 +69,6 @@ public Options getOptions() { @Override public void runImpl(CommandLine cli) throws Exception { - String zkHost = CLIUtils.getZkHost(cli); String path = cli.getArgs()[0]; @@ -67,13 +82,30 @@ public void runImpl(CommandLine cli) throws Exception { .withUrl(zkHost) .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) .build()) { - - zkClient.updateACLs(path); + doUpdateAcls(zkClient, path); } } + private void doUpdateAcls(SolrZkClient zkClient, String path) throws Exception { + zkClient.updateACLs(path); + } + @Override public int callTool() throws Exception { - throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + String zkHost = zkOpts.resolveZkHost(); + + if (!ZkController.checkChrootPath(zkHost, true)) { + throw new IllegalStateException( + "A chroot was specified in zkHost but the znode doesn't exist."); + } + + try (SolrZkClient zkClient = + new SolrZkClient.Builder() + .withUrl(zkHost) + .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) + .build()) { + doUpdateAcls(zkClient, path); + return 0; + } } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java b/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java index 3e7f912f2485..e28a57715f6d 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java @@ -39,6 +39,18 @@ import org.slf4j.LoggerFactory; /** Supports zk cp command in the bin/solr script. */ +@picocli.CommandLine.Command( + name = "cp", + mixinStandardHelpOptions = true, + description = { + "Copy files or folders to/from ZooKeeper or ZooKeeper to ZooKeeper.", + "", + ", : [file:][/]path/to/local/file or zk:/path/to/zk/node", + "NOTE: and may both be ZooKeeper resources prefixed by 'zk:'", + "When is a zk resource, may be '.'", + "If ends with '/', then will be a local folder or parent znode", + "and the last element of the path will be appended unless also ends in a slash." + }) public class ZkCpTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -50,6 +62,32 @@ public class ZkCpTool extends ToolBase { .desc("Required to look up configuration for compressing state.json.") .get(); + @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + + @picocli.CommandLine.Parameters( + index = "0", + arity = "1", + description = "Source path: [file:][/]path/to/local/file or zk:/path/to/zk/node.") + private String src; + + @picocli.CommandLine.Parameters( + index = "1", + arity = "1", + description = "Destination path: [file:][/]path/to/local/file or zk:/path/to/zk/node.") + private String dst; + + @picocli.CommandLine.Mixin RecursiveOption recursiveOpt; + + @picocli.CommandLine.Option( + names = {"--solr-home"}, + description = "Required to look up configuration for compressing state.json.", + paramLabel = "DIR") + private String solrHome; + + public ZkCpTool() { + this(new DefaultToolRuntime()); + } + public ZkCpTool(ToolRuntime runtime) { super(runtime); } @@ -119,11 +157,16 @@ public String getHeader() { @Override public void runImpl(CommandLine cli) throws Exception { String zkHost = CLIUtils.getZkHost(cli); - - echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); String src = cli.getArgs()[0]; String dst = cli.getArgs()[1]; boolean recursive = cli.hasOption(CommonCLIOptions.RECURSIVE_OPTION); + String solrHome = cli.getOptionValue(SOLR_HOME_OPTION); + doCp(zkHost, src, dst, recursive, solrHome); + } + + private void doCp(String zkHost, String src, String dst, boolean recursive, String solrHome) + throws Exception { + echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); echo("Copying from '" + src + "' to '" + dst + "'. ZooKeeper at " + zkHost); boolean srcIsZk = src.toLowerCase(Locale.ROOT).startsWith("zk:"); @@ -152,7 +195,6 @@ public void runImpl(CommandLine cli) throws Exception { Compressor compressor = new ZLibCompressor(); if (dstIsZk) { - String solrHome = cli.getOptionValue(SOLR_HOME_OPTION); if (StrUtils.isNullOrEmpty(solrHome)) { solrHome = System.getProperty("solr.home"); } @@ -210,6 +252,7 @@ public void runImpl(CommandLine cli) throws Exception { @Override public int callTool() throws Exception { - throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + doCp(zkOpts.resolveZkHost(), src, dst, recursiveOpt.recursive, solrHome); + return 0; } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java b/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java index cd09844e05c1..3f0b87db1627 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java @@ -18,33 +18,30 @@ import java.lang.invoke.MethodHandles; import java.util.concurrent.TimeUnit; -import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.solr.client.solrj.impl.SolrZkClientTimeout; import org.apache.solr.common.cloud.SolrZkClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import picocli.CommandLine; /** Supports zk ls command in the bin/solr script. */ -@picocli.CommandLine.Command( +@CommandLine.Command( name = "ls", mixinStandardHelpOptions = true, description = "List the contents of a ZooKeeper node.") public class ZkLsTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + @CommandLine.Mixin ZkConnectionOptions zkOpts; - @picocli.CommandLine.Parameters( + @CommandLine.Parameters( index = "0", arity = "1", description = "The path of the ZooKeeper znode path to list.") private String path; - @picocli.CommandLine.Option( - names = {"-r", "--recursive"}, - description = "Apply the command recursively.") - private boolean recursive; + @CommandLine.Mixin RecursiveOption recursiveOpt; public ZkLsTool() { this(new DefaultToolRuntime()); @@ -74,37 +71,30 @@ public String getUsage() { } @Override - public void runImpl(CommandLine cli) throws Exception { + public void runImpl(org.apache.commons.cli.CommandLine cli) throws Exception { String zkHost = CLIUtils.getZkHost(cli); String znode = cli.getArgs()[0]; + boolean recursive = cli.hasOption(CommonCLIOptions.RECURSIVE_OPTION); try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) { - echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); - boolean recursive = cli.hasOption(CommonCLIOptions.RECURSIVE_OPTION); - echoIfVerbose( - "Getting listing for ZooKeeper node " - + znode - + " from ZooKeeper at " - + zkHost - + " recursive: " - + recursive); - runtime.print(zkClient.listZnode(znode, recursive)); + doLs(zkClient, zkHost, znode, recursive); } catch (Exception e) { log.error("Could not complete ls operation for reason: ", e); throw (e); } } - private void doLs(SolrZkClient zkClient) throws Exception { - echoIfVerbose("\nConnecting to ZooKeeper at " + zkOpts.zkHost + " ..."); + private void doLs(SolrZkClient zkClient, String zkHost, String znode, boolean recursive) + throws Exception { + echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); echoIfVerbose( "Getting listing for ZooKeeper node " - + path + + znode + " from ZooKeeper at " - + zkOpts.zkHost + + zkHost + " recursive: " + recursive); - runtime.print(zkClient.listZnode(path, recursive)); + runtime.print(zkClient.listZnode(znode, recursive)); } @Override @@ -116,7 +106,7 @@ public int callTool() throws Exception { .withUrl(zkHost) .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) .build()) { - doLs(zkClient); + doLs(zkClient, zkHost, path, recursiveOpt.recursive); return 0; } catch (Exception e) { log.error("Could not complete ls operation for reason: ", e); diff --git a/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java b/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java index 5c0d51a30c19..62774298af05 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java @@ -19,14 +19,22 @@ import static org.apache.solr.packagemanager.PackageUtils.format; import java.lang.invoke.MethodHandles; +import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.solr.client.solrj.impl.SolrZkClientTimeout; import org.apache.solr.common.cloud.SolrZkClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Supports zk mkroot command in the bin/solr script. */ +@picocli.CommandLine.Command( + name = "mkroot", + mixinStandardHelpOptions = true, + description = + "Make a znode in ZooKeeper with no data. Can be used to make a path of arbitrary depth" + + " but primarily intended to create a 'chroot'.") public class ZkMkrootTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -37,6 +45,25 @@ public class ZkMkrootTool extends ToolBase { .desc("Raise an error if the root exists. Defaults to false.") .get(); + @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + + @picocli.CommandLine.Parameters( + index = "0", + arity = "1", + description = "The ZooKeeper znode path to create.") + private String path; + + @picocli.CommandLine.Option( + names = {"--fail-on-exists"}, + arity = "0..1", + fallbackValue = "true", + description = "Raise an error if the znode already exists. Defaults to false.") + private boolean failOnExists; + + public ZkMkrootTool() { + this(new DefaultToolRuntime()); + } + public ZkMkrootTool(ToolRuntime runtime) { super(runtime); } @@ -76,18 +103,34 @@ public void runImpl(CommandLine cli) throws Exception { boolean failOnExists = cli.hasOption(FAIL_ON_EXISTS_OPTION); try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) { - echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); - - echo("Creating ZooKeeper path " + znode + " on ZooKeeper at " + zkHost); - zkClient.makePath(znode, failOnExists); + doMkroot(zkClient, zkHost, znode, failOnExists); } catch (Exception e) { log.error("Could not complete mkroot operation for reason: ", e); throw (e); } } + private void doMkroot(SolrZkClient zkClient, String zkHost, String znode, boolean failOnExists) + throws Exception { + echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); + echo("Creating ZooKeeper path " + znode + " on ZooKeeper at " + zkHost); + zkClient.makePath(znode, failOnExists); + } + @Override public int callTool() throws Exception { - throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + String zkHost = zkOpts.resolveZkHost(); + + try (SolrZkClient zkClient = + new SolrZkClient.Builder() + .withUrl(zkHost) + .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) + .build()) { + doMkroot(zkClient, zkHost, path, failOnExists); + return 0; + } catch (Exception e) { + log.error("Could not complete mkroot operation for reason: ", e); + throw (e); + } } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java b/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java index 759eacb1b7b9..33be052767e2 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java @@ -20,17 +20,47 @@ import java.lang.invoke.MethodHandles; import java.util.Locale; +import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.SolrZkClientTimeout; import org.apache.solr.common.cloud.SolrZkClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Supports zk mv command in the bin/solr script. */ +@picocli.CommandLine.Command( + name = "mv", + mixinStandardHelpOptions = true, + description = { + "Move (rename) a znode on ZooKeeper.", + "", + ", : ZooKeeper nodes, the 'zk:' prefix is optional.", + "If ends with '/', then will be a parent znode", + "and the last element of the path will be appended." + }) public class ZkMvTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + + @picocli.CommandLine.Parameters( + index = "0", + arity = "1", + description = "Source ZooKeeper znode path (zk: prefix optional).") + private String src; + + @picocli.CommandLine.Parameters( + index = "1", + arity = "1", + description = "Destination ZooKeeper znode path (zk: prefix optional).") + private String dst; + + public ZkMvTool() { + this(new DefaultToolRuntime()); + } + public ZkMvTool(ToolRuntime runtime) { super(runtime); } @@ -70,37 +100,52 @@ public String getHeader() { @Override public void runImpl(CommandLine cli) throws Exception { String zkHost = CLIUtils.getZkHost(cli); + String src = cli.getArgs()[0]; + String dst = cli.getArgs()[1]; try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) { - echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); - String src = cli.getArgs()[0]; - String dst = cli.getArgs()[1]; - - if (src.toLowerCase(Locale.ROOT).startsWith("file:") - || dst.toLowerCase(Locale.ROOT).startsWith("file:")) { - throw new SolrServerException( - "mv command operates on znodes and 'file:' has been specified."); - } - String source = src; - if (src.toLowerCase(Locale.ROOT).startsWith("zk")) { - source = src.substring(3); - } - - String dest = dst; - if (dst.toLowerCase(Locale.ROOT).startsWith("zk")) { - dest = dst.substring(3); - } - - echo("Moving Znode " + source + " to " + dest + " on ZooKeeper at " + zkHost); - zkClient.moveZnode(source, dest); + doMv(zkClient, zkHost, src, dst); } catch (Exception e) { log.error("Could not complete mv operation for reason: ", e); throw (e); } } + private void doMv(SolrZkClient zkClient, String zkHost, String src, String dst) throws Exception { + echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); + if (src.toLowerCase(Locale.ROOT).startsWith("file:") + || dst.toLowerCase(Locale.ROOT).startsWith("file:")) { + throw new SolrServerException( + "mv command operates on znodes and 'file:' has been specified."); + } + String source = src; + if (src.toLowerCase(Locale.ROOT).startsWith("zk:")) { + source = src.substring(3); + } + + String dest = dst; + if (dst.toLowerCase(Locale.ROOT).startsWith("zk:")) { + dest = dst.substring(3); + } + + echo("Moving Znode " + source + " to " + dest + " on ZooKeeper at " + zkHost); + zkClient.moveZnode(source, dest); + } + @Override public int callTool() throws Exception { - throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + String zkHost = zkOpts.resolveZkHost(); + + try (SolrZkClient zkClient = + new SolrZkClient.Builder() + .withUrl(zkHost) + .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) + .build()) { + doMv(zkClient, zkHost, src, dst); + return 0; + } catch (Exception e) { + log.error("Could not complete mv operation for reason: ", e); + throw (e); + } } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java b/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java index 8aab732c6f8b..5d683e56eac1 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java @@ -18,17 +18,37 @@ import java.lang.invoke.MethodHandles; import java.util.Locale; +import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.SolrZkClientTimeout; import org.apache.solr.common.cloud.SolrZkClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Supports zk rm command in the bin/solr script. */ +@picocli.CommandLine.Command( + name = "rm", + mixinStandardHelpOptions = true, + description = "Remove a znode from ZooKeeper.") public class ZkRmTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @picocli.CommandLine.Mixin ZkConnectionOptions zkOpts; + + @picocli.CommandLine.Parameters( + index = "0", + arity = "1", + description = "The ZooKeeper znode path to remove (zk: prefix optional).") + private String path; + + @picocli.CommandLine.Mixin RecursiveOption recursiveOpt; + + public ZkRmTool() { + this(new DefaultToolRuntime()); + } + public ZkRmTool(ToolRuntime runtime) { super(runtime); } @@ -54,10 +74,20 @@ public String getUsage() { @Override public void runImpl(CommandLine cli) throws Exception { String zkHost = CLIUtils.getZkHost(cli); - String target = cli.getArgs()[0]; boolean recursive = cli.hasOption(CommonCLIOptions.RECURSIVE_OPTION); + echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); + try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) { + doRm(zkClient, zkHost, target, recursive); + } catch (Exception e) { + log.error("Could not complete rm operation for reason: ", e); + throw (e); + } + } + + private void doRm(SolrZkClient zkClient, String zkHost, String target, boolean recursive) + throws Exception { String znode = target; if (target.toLowerCase(Locale.ROOT).startsWith("zk:")) { znode = target.substring(3); @@ -65,28 +95,34 @@ public void runImpl(CommandLine cli) throws Exception { if (znode.equals("/")) { throw new SolrServerException("You may not remove the root ZK node ('/')!"); } - echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ..."); - try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) { - if (!recursive && !zkClient.getChildren(znode, null).isEmpty()) { - throw new SolrServerException( - "ZooKeeper node " + znode + " has children and recursive has NOT been specified."); - } - echo( - "Removing ZooKeeper node " - + znode - + " from ZooKeeper at " - + zkHost - + " recursive: " - + recursive); - zkClient.clean(znode); - } catch (Exception e) { - log.error("Could not complete rm operation for reason: ", e); - throw (e); + if (!recursive && !zkClient.getChildren(znode, null).isEmpty()) { + throw new SolrServerException( + "ZooKeeper node " + znode + " has children and recursive has NOT been specified."); } + echo( + "Removing ZooKeeper node " + + znode + + " from ZooKeeper at " + + zkHost + + " recursive: " + + recursive); + zkClient.clean(znode); } @Override public int callTool() throws Exception { - throw new UnsupportedOperationException("This tool does not yet support PicoCli"); + String zkHost = zkOpts.resolveZkHost(); + + try (SolrZkClient zkClient = + new SolrZkClient.Builder() + .withUrl(zkHost) + .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS) + .build()) { + doRm(zkClient, zkHost, path, recursiveOpt.recursive); + return 0; + } catch (Exception e) { + log.error("Could not complete rm operation for reason: ", e); + throw (e); + } } } diff --git a/solr/core/src/java/org/apache/solr/cli/ZkTool.java b/solr/core/src/java/org/apache/solr/cli/ZkTool.java index af69c2461e25..1c744d43382f 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkTool.java @@ -27,7 +27,17 @@ name = "zk", mixinStandardHelpOptions = true, description = "Sub commands for working with ZooKeeper.", - subcommands = {ZkLsTool.class}) + footer = "\nPass --help or -h after any COMMAND to see command-specific usage information.", + subcommands = { + ConfigSetDownloadTool.class, + ConfigSetUploadTool.class, + ZkCpTool.class, + ZkLsTool.class, + ZkMkrootTool.class, + ZkMvTool.class, + ZkRmTool.class, + UpdateACLTool.class + }) public class ZkTool implements Callable { @CommandLine.Spec CommandLine.Model.CommandSpec spec; diff --git a/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsPicocliTest.java b/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsPicocliTest.java new file mode 100644 index 000000000000..39b593d7dc72 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsPicocliTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import java.util.Arrays; + +/** + * Runs all {@link ZkSubcommandsTest} tests through the picocli invocation path. + * + *

All {@code @Test} methods are inherited from {@link ZkSubcommandsTest}. Only the tool + * invocation strategy is overridden here to use {@code picocli.CommandLine.execute()} instead of + * the commons-cli path. + */ +public class ZkSubcommandsPicocliTest extends ZkSubcommandsTest { + + @Override + protected int runTool(String[] args, Class clazz) throws Exception { + // args[0] is the tool/subcommand name used by commons-cli dispatch; strip it for picocli. + String[] toolArgs = Arrays.copyOfRange(args, 1, args.length); + ToolBase tool = clazz.getDeclaredConstructor().newInstance(); + return new picocli.CommandLine(tool) + .setDefaultValueProvider(new CliDefaultValueProvider()) + .execute(toolArgs); + } + + @Override + protected int runTool( + String[] args, CLITestHelper.TestingRuntime runtime, Class clazz) + throws Exception { + String[] toolArgs = Arrays.copyOfRange(args, 1, args.length); + ToolBase tool = clazz.getDeclaredConstructor(ToolRuntime.class).newInstance(runtime); + return new picocli.CommandLine(tool) + .setDefaultValueProvider(new CliDefaultValueProvider()) + .execute(toolArgs); + } +} diff --git a/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsTest.java b/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsTest.java index d5aa1a72d5ae..4aebf94ee765 100644 --- a/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsTest.java +++ b/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsTest.java @@ -46,6 +46,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Tests ZK subcommands by invoking tool instances directly (not through the bin/solr script). + * + *

Subclasses override {@link #runTool} to exercise different invocation paths (commons-cli vs + * picocli). All {@code @Test} methods are inherited and run in each subclass. + * + * @see ZkSubcommandsPicocliTest + */ public class ZkSubcommandsTest extends SolrTestCaseJ4 { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -89,6 +97,29 @@ public void setUp() throws Exception { } } + /** + * Runs a tool via the invocation path under test. {@code args[0]} is the tool name. + * + *

The default implementation uses commons-cli via {@link CLITestHelper#runTool}. Subclasses + * override this to exercise a different invocation path (e.g. picocli). + */ + protected int runTool(String[] args, Class clazz) throws Exception { + return CLITestHelper.runTool(args, clazz); + } + + /** + * Runs a tool with output capture via the invocation path under test. {@code args[0]} is the tool + * name. + * + *

The default implementation uses commons-cli via {@link CLITestHelper#runTool}. Subclasses + * override this to exercise a different invocation path (e.g. picocli). + */ + protected int runTool( + String[] args, CLITestHelper.TestingRuntime runtime, Class clazz) + throws Exception { + return CLITestHelper.runTool(args, runtime, clazz); + } + @Test public void testPut() throws Exception { // test put @@ -101,7 +132,7 @@ public void testPut() throws Exception { String[] args = new String[] {"cp", "-z", zkServer.getZkAddress(), localFile.toString(), "zk:/data.txt"}; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); assertArrayEquals( zkClient.getData("/data.txt", null, null), data.getBytes(StandardCharsets.UTF_8)); @@ -114,7 +145,7 @@ public void testPut() throws Exception { writer.write(data); writer.close(); - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); assertArrayEquals( zkClient.getData("/data.txt", null, null), data.getBytes(StandardCharsets.UTF_8)); @@ -139,7 +170,7 @@ public void testPutCompressed() throws Exception { String[] args = new String[] {"cp", "-z", zkServer.getZkAddress(), localFile.toString(), "zk:/state.json"}; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); assertArrayEquals(dataBytes, zkClient.getCuratorFramework().getData().forPath("/state.json")); assertArrayEquals( @@ -160,7 +191,7 @@ public void testPutCompressed() throws Exception { args = new String[] {"cp", "-z", zkServer.getZkAddress(), localFile.toString(), "zk:/state.json"}; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); byte[] fromZkRaw = zkClient.getCuratorFramework().getData().undecompressed().forPath("/state.json"); @@ -193,7 +224,7 @@ public void testPutFile() throws Exception { "zk:/foo.xml" }; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); String fromZk = new String(zkClient.getData("/foo.xml", null, null), StandardCharsets.UTF_8); Path localFile = SOLR_HOME.resolve("solr-stress-new.xml"); @@ -213,7 +244,7 @@ public void testPutFileWithoutSlash() throws Exception { "zk:foo.xml" }; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); String fromZk = new String(zkClient.getData("/foo.xml", null, null), StandardCharsets.UTF_8); Path localFile = SOLR_HOME.resolve("solr-stress-new.xml"); @@ -235,7 +266,7 @@ public void testPutFileCompressed() throws Exception { "zk:/state.json" }; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); Path locFile = SOLR_HOME.resolve("solr-stress-new.xml"); byte[] fileBytes = Files.readAllBytes(locFile); @@ -252,7 +283,7 @@ public void testPutFileCompressed() throws Exception { "Should get back an uncompressed version what we put in ZK", fileBytes, fromZk); // Let's do it again - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); locFile = SOLR_HOME.resolve("solr-stress-new.xml"); fileBytes = Files.readAllBytes(locFile); @@ -279,7 +310,7 @@ public void testPutFileNotExists() throws Exception { "zk:/foo.xml" }; - assertEquals(1, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(1, runTool(args, ZkCpTool.class)); } @Test @@ -287,10 +318,10 @@ public void testLs() throws Exception { zkClient.makePath("/test/path", true); // test what happens when path arg "/" isn't the last one. - String[] args = new String[] {"ls", "/", "-r", "true", "-z", zkServer.getZkAddress()}; + String[] args = new String[] {"ls", "/", "-r", "-z", zkServer.getZkAddress()}; CLITestHelper.TestingRuntime runtime = new CLITestHelper.TestingRuntime(true); - assertEquals(0, CLITestHelper.runTool(args, runtime, ZkLsTool.class)); + assertEquals(0, runTool(args, runtime, ZkLsTool.class)); final String standardOutput2 = runtime.getOutput(); @@ -318,7 +349,7 @@ public void testUpConfigDownConfigClearZk() throws Exception { zkServer.getZkAddress() }; - assertEquals(0, CLITestHelper.runTool(args, ConfigSetUploadTool.class)); + assertEquals(0, runTool(args, ConfigSetUploadTool.class)); assertTrue(zkClient.exists(ZkConfigSetService.CONFIGS_ZKNODE + "/" + confsetname)); final Path confDir = ExternalPaths.TECHPRODUCTS_CONFIGSET; @@ -349,7 +380,7 @@ public void testUpConfigDownConfigClearZk() throws Exception { zkServer.getZkAddress() }; - assertEquals(0, CLITestHelper.runTool(args, ConfigSetDownloadTool.class)); + assertEquals(0, runTool(args, ConfigSetDownloadTool.class)); try (Stream filesStream = Files.list(destDir.resolve("conf"))) { List files = filesStream.toList(); @@ -395,7 +426,7 @@ public void testUpConfigDownConfigClearZk() throws Exception { // test reset zk args = new String[] {"rm", "-r", "-z", zkServer.getZkAddress(), "zk:/configs/confsetone"}; - assertEquals(0, CLITestHelper.runTool(args, ZkRmTool.class)); + assertEquals(0, runTool(args, ZkRmTool.class)); assertEquals(0, zkClient.getChildren("/configs", null).size()); } @@ -412,7 +443,7 @@ public void testGet() throws Exception { new String[] {"cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, localFile.toString()}; CLITestHelper.TestingRuntime runtime = new CLITestHelper.TestingRuntime(true); - assertEquals(0, CLITestHelper.runTool(args, runtime, ZkCpTool.class)); + assertEquals(0, runTool(args, runtime, ZkCpTool.class)); final String standardOutput2 = runtime.getOutput(); assertTrue(standardOutput2.startsWith("Copying from 'zk:/getNode'")); @@ -438,7 +469,7 @@ public void testGetCompressed() throws Exception { String[] args = new String[] {"cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, localFile.toString()}; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); assertArrayEquals(data, Files.readAllBytes(localFile)); } @@ -457,7 +488,7 @@ public void testGetFile() throws Exception { // Not setting --zk-host, will fall back to sysProp 'zkHost' String[] args = new String[] {"cp", "zk:" + getNode, file.toString()}; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); assertArrayEquals(data, Files.readAllBytes(file)); } @@ -480,7 +511,7 @@ public void testGetFileCompressed() throws Exception { String[] args = new String[] {"cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, file.toString()}; - assertEquals(0, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(0, runTool(args, ZkCpTool.class)); assertArrayEquals(data, Files.readAllBytes(file)); } @@ -494,19 +525,72 @@ public void testGetFileNotExists() throws Exception { String[] args = new String[] {"cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, file.toString()}; - assertEquals(1, CLITestHelper.runTool(args, ZkCpTool.class)); + assertEquals(1, runTool(args, ZkCpTool.class)); } @Test public void testInvalidZKAddress() throws Exception { - String[] args = new String[] {"ls", "/", "-r", "-z", "----------:33332"}; + String[] args = new String[] {"ls", "-r", "-z", "----------:33332", "/"}; + + assertEquals(1, runTool(args, ZkLsTool.class)); + } + + @Test + public void testMkroot() throws Exception { + String[] args = new String[] {"mkroot", "-z", zkServer.getZkAddress(), "/mkroot-test/a/b/c"}; + + assertEquals(0, runTool(args, ZkMkrootTool.class)); + assertTrue("Created path should exist", zkClient.exists("/mkroot-test/a/b/c")); - assertEquals(1, CLITestHelper.runTool(args, ZkLsTool.class)); + // --fail-on-exists with a value works for both commons-cli (hasArg) and picocli (arity 0..1) + args = + new String[] { + "mkroot", "--fail-on-exists", "true", "-z", zkServer.getZkAddress(), "/mkroot-test/a/b/c" + }; + assertEquals(1, runTool(args, ZkMkrootTool.class)); + + // Without --fail-on-exists, creating an existing path should succeed + args = new String[] {"mkroot", "-z", zkServer.getZkAddress(), "/mkroot-test/a/b/c"}; + assertEquals(0, runTool(args, ZkMkrootTool.class)); + } + + @Test + public void testMv() throws Exception { + zkClient.makePath("/mv-src", true); + + String[] args = new String[] {"mv", "-z", zkServer.getZkAddress(), "/mv-src", "/mv-dst"}; + assertEquals(0, runTool(args, ZkMvTool.class)); + assertFalse("Source should be gone after mv", zkClient.exists("/mv-src")); + assertTrue("Destination should exist after mv", zkClient.exists("/mv-dst")); + + // file: prefix should cause failure + args = new String[] {"mv", "-z", zkServer.getZkAddress(), "file:/mv-dst", "/mv-dst2"}; + assertEquals(1, runTool(args, ZkMvTool.class)); + } + + @Test + public void testRm() throws Exception { + zkClient.makePath("/rm-test/child", true); + + // Should fail without --recursive when node has children + String[] args = new String[] {"rm", "-z", zkServer.getZkAddress(), "/rm-test"}; + assertEquals(1, runTool(args, ZkRmTool.class)); + assertTrue("Node should still exist after failed rm", zkClient.exists("/rm-test")); + + // Should succeed with --recursive + args = new String[] {"rm", "-r", "-z", zkServer.getZkAddress(), "/rm-test"}; + assertEquals(0, runTool(args, ZkRmTool.class)); + assertFalse("Node should be gone after rm -r", zkClient.exists("/rm-test")); + + // Removing root should fail + args = new String[] {"rm", "-r", "-z", zkServer.getZkAddress(), "zk:/"}; + assertEquals(1, runTool(args, ZkRmTool.class)); } @Test public void testSetClusterProperty() throws Exception { + // ClusterTool is not a picocli ZK subcommand; always use the commons-cli path. ClusterProperties properties = new ClusterProperties(zkClient); // add property urlScheme=http String[] args = @@ -539,7 +623,7 @@ public void testUpdateAcls() throws Exception { VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME, "pass"); String[] args = new String[] {"updateacls", "/", "-z", zkServer.getZkAddress()}; - assertEquals(0, CLITestHelper.runTool(args, UpdateACLTool.class)); + assertEquals(0, runTool(args, UpdateACLTool.class)); boolean excepted = false; try (SolrZkClient zkClient = diff --git a/solr/packaging/build.gradle b/solr/packaging/build.gradle index 7320f2ebe2b2..fa8fef586022 100644 --- a/solr/packaging/build.gradle +++ b/solr/packaging/build.gradle @@ -309,6 +309,8 @@ task integrationTests(type: BatsTask) { environment TEST_OUTPUT_DIR: integrationTestOutput environment TEST_FAILURE_DIR: solrTestFailuresDir environment BATS_LIB_PREFIX: "$nodeProjectDir/node_modules" + // TODO: SOLR-17697 picocli, run all BATS tests using picocli. + //environment SOLR_PICOCLI: 'true' } class BatsTask extends Exec { diff --git a/solr/packaging/test/test_zk.bats b/solr/packaging/test/test_zk.bats index 11ee3a7a76d1..66178030c117 100644 --- a/solr/packaging/test/test_zk.bats +++ b/solr/packaging/test/test_zk.bats @@ -48,15 +48,18 @@ teardown() { @test "long help" { run solr zk -h - assert_output --partial "bin/solr zk ls" - assert_output --partial "bin/solr zk updateacls" + # TODO: Long help is different in picocli + #assert_output --partial "bin/solr zk ls" + #assert_output --partial "bin/solr zk updateacls" assert_output --partial "Pass --help or -h after any COMMAND" + assert_output --partial "updateacls" } -@test "running subcommands with zk is prevented" { - run solr ls / -z localhost:${ZK_PORT} - assert_output --partial "You must invoke this subcommand using the zk command" -} +# TODO: Picocli fails in a different way when a subcommand is not invoked using the zk command +#@test "running subcommands with zk is prevented" { +# run solr ls / -z localhost:${ZK_PORT} +# assert_output --partial "You must invoke this subcommand using the zk command" +#} @test "listing out files" { sleep 1 From 5cd2ab398cf7b78b16dde08c5478c1ee7ee5ad55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Thu, 9 Apr 2026 12:02:24 +0200 Subject: [PATCH 7/7] SOLR-17697 Remove --version option for all tools except top-level bin/solr (#4274) --- .../solr/cli/ConfigSetDownloadTool.java | 1 - .../apache/solr/cli/ConfigSetUploadTool.java | 1 - .../java/org/apache/solr/cli/HelpMixin.java | 32 +++++++++++++++++++ .../src/java/org/apache/solr/cli/SolrCLI.java | 4 --- .../org/apache/solr/cli/StartCommand.java | 7 ++-- .../java/org/apache/solr/cli/StatusTool.java | 5 +-- .../java/org/apache/solr/cli/StopCommand.java | 4 ++- .../java/org/apache/solr/cli/ToolBase.java | 2 ++ .../org/apache/solr/cli/UpdateACLTool.java | 1 - .../java/org/apache/solr/cli/ZkCpTool.java | 1 - .../java/org/apache/solr/cli/ZkLsTool.java | 5 +-- .../org/apache/solr/cli/ZkMkrootTool.java | 1 - .../java/org/apache/solr/cli/ZkMvTool.java | 1 - .../java/org/apache/solr/cli/ZkRmTool.java | 5 +-- .../src/java/org/apache/solr/cli/ZkTool.java | 3 +- 15 files changed, 45 insertions(+), 28 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/cli/HelpMixin.java diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java b/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java index aa5b3c91fac6..0d812de23fb9 100644 --- a/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java @@ -31,7 +31,6 @@ /** Supports zk downconfig command in the bin/solr script. */ @picocli.CommandLine.Command( name = "downconfig", - mixinStandardHelpOptions = true, description = "Download a configset from ZooKeeper to the local filesystem.") public class ConfigSetDownloadTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java b/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java index 87bfd2fb87a4..156681b48891 100644 --- a/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java @@ -33,7 +33,6 @@ /** Supports zk upconfig command in the bin/solr script. */ @picocli.CommandLine.Command( name = "upconfig", - mixinStandardHelpOptions = true, description = "Upload a configset from the local filesystem to ZooKeeper.") public class ConfigSetUploadTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); diff --git a/solr/core/src/java/org/apache/solr/cli/HelpMixin.java b/solr/core/src/java/org/apache/solr/cli/HelpMixin.java new file mode 100644 index 000000000000..57e2caae50a6 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cli/HelpMixin.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.cli; + +import picocli.CommandLine; + +/** + * Picocli mixin that adds a {@code --help} option to a command without also adding {@code + * --version}. Prefer this over {@code mixinStandardHelpOptions = true} on subcommands, since {@code + * --version} is only meaningful on the top-level {@code bin/solr} command. + */ +public class HelpMixin { + @CommandLine.Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Print this help message and exit.") + public boolean help; +} diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java index 12e1ce518cb8..9f62a90868c3 100755 --- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java +++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java @@ -60,7 +60,6 @@ mixinStandardHelpOptions = true, synopsisHeading = "usage: ", commandListHeading = "\nCommands:\n", - descriptionHeading = "Global options:\n", footer = { "", "SolrCloud example (embedded Zookeeper):", @@ -111,9 +110,6 @@ public static void main(String[] args) throws Exception { private static void propagateCommandSettings(picocli.CommandLine cmd) { for (picocli.CommandLine subcommand : cmd.getSubcommands().values()) { subcommand.getCommandSpec().defaultValueProvider(cmd.getCommandSpec().defaultValueProvider()); - subcommand - .getCommandSpec() - .mixinStandardHelpOptions(cmd.getCommandSpec().mixinStandardHelpOptions()); subcommand.getCommandSpec().usageMessage().width(cmd.getCommandSpec().usageMessage().width()); subcommand .getCommandSpec() diff --git a/solr/core/src/java/org/apache/solr/cli/StartCommand.java b/solr/core/src/java/org/apache/solr/cli/StartCommand.java index b7c0b3f07c5b..ed173a42beef 100644 --- a/solr/core/src/java/org/apache/solr/cli/StartCommand.java +++ b/solr/core/src/java/org/apache/solr/cli/StartCommand.java @@ -19,12 +19,11 @@ import java.util.concurrent.Callable; import picocli.CommandLine; -@CommandLine.Command( - name = "start", - description = "Starts Solr in standalone or SolrCloud mode.", - mixinStandardHelpOptions = true) +@CommandLine.Command(name = "start", description = "Starts Solr in standalone or SolrCloud mode.") public class StartCommand implements Callable { + @CommandLine.Mixin HelpMixin helpMixin; + @CommandLine.Spec CommandLine.Model.CommandSpec spec; @CommandLine.Option( diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java b/solr/core/src/java/org/apache/solr/cli/StatusTool.java index bbe253a6887f..081bd57bca41 100644 --- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java @@ -44,10 +44,7 @@ * *

Get the status of a Solr server. */ -@picocli.CommandLine.Command( - name = "status", - mixinStandardHelpOptions = true, - description = "Get the status of a Solr server.") +@picocli.CommandLine.Command(name = "status", description = "Get the status of a Solr server.") public class StatusTool extends ToolBase { @picocli.CommandLine.Option( names = {"--max-wait-secs"}, diff --git a/solr/core/src/java/org/apache/solr/cli/StopCommand.java b/solr/core/src/java/org/apache/solr/cli/StopCommand.java index f9f3fc626280..0803cea767ac 100644 --- a/solr/core/src/java/org/apache/solr/cli/StopCommand.java +++ b/solr/core/src/java/org/apache/solr/cli/StopCommand.java @@ -22,9 +22,11 @@ * This class is currently only used for printing CLI usage. The stop logic is currently handled in * start script. */ -@CommandLine.Command(name = "stop", description = "Stops Solr.", mixinStandardHelpOptions = true) +@CommandLine.Command(name = "stop", description = "Stops Solr.") public class StopCommand { + @CommandLine.Mixin HelpMixin helpMixin; + @CommandLine.Option( names = {"-p", "--port"}, description = diff --git a/solr/core/src/java/org/apache/solr/cli/ToolBase.java b/solr/core/src/java/org/apache/solr/cli/ToolBase.java index 8b3d4304cf3c..c05595c94f62 100644 --- a/solr/core/src/java/org/apache/solr/cli/ToolBase.java +++ b/solr/core/src/java/org/apache/solr/cli/ToolBase.java @@ -26,6 +26,8 @@ import org.apache.solr.util.StartupLoggingUtils; public abstract class ToolBase implements Tool, Callable { + @picocli.CommandLine.Mixin HelpMixin helpMixin; + @picocli.CommandLine.Option( names = {"-v", "--verbose"}, description = "Enable verbose mode.") diff --git a/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java b/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java index cb79d0bf42c2..c0ebbe449022 100644 --- a/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java +++ b/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java @@ -31,7 +31,6 @@ */ @picocli.CommandLine.Command( name = "updateacls", - mixinStandardHelpOptions = true, description = "Update ACLs for a ZooKeeper znode.") public class UpdateACLTool extends ToolBase { // It is a shame this tool doesn't more closely mimic how the ConfigTool works. diff --git a/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java b/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java index e28a57715f6d..292f88c39f41 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java @@ -41,7 +41,6 @@ /** Supports zk cp command in the bin/solr script. */ @picocli.CommandLine.Command( name = "cp", - mixinStandardHelpOptions = true, description = { "Copy files or folders to/from ZooKeeper or ZooKeeper to ZooKeeper.", "", diff --git a/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java b/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java index 3f0b87db1627..56e4ef21b7de 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java @@ -26,10 +26,7 @@ import picocli.CommandLine; /** Supports zk ls command in the bin/solr script. */ -@CommandLine.Command( - name = "ls", - mixinStandardHelpOptions = true, - description = "List the contents of a ZooKeeper node.") +@CommandLine.Command(name = "ls", description = "List the contents of a ZooKeeper node.") public class ZkLsTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); diff --git a/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java b/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java index 62774298af05..82573f74dc58 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java @@ -31,7 +31,6 @@ /** Supports zk mkroot command in the bin/solr script. */ @picocli.CommandLine.Command( name = "mkroot", - mixinStandardHelpOptions = true, description = "Make a znode in ZooKeeper with no data. Can be used to make a path of arbitrary depth" + " but primarily intended to create a 'chroot'.") diff --git a/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java b/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java index 33be052767e2..9036e461bfc8 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java @@ -32,7 +32,6 @@ /** Supports zk mv command in the bin/solr script. */ @picocli.CommandLine.Command( name = "mv", - mixinStandardHelpOptions = true, description = { "Move (rename) a znode on ZooKeeper.", "", diff --git a/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java b/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java index 5d683e56eac1..ed529066a108 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java @@ -28,10 +28,7 @@ import org.slf4j.LoggerFactory; /** Supports zk rm command in the bin/solr script. */ -@picocli.CommandLine.Command( - name = "rm", - mixinStandardHelpOptions = true, - description = "Remove a znode from ZooKeeper.") +@picocli.CommandLine.Command(name = "rm", description = "Remove a znode from ZooKeeper.") public class ZkRmTool extends ToolBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); diff --git a/solr/core/src/java/org/apache/solr/cli/ZkTool.java b/solr/core/src/java/org/apache/solr/cli/ZkTool.java index 1c744d43382f..bed1805853ca 100644 --- a/solr/core/src/java/org/apache/solr/cli/ZkTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ZkTool.java @@ -25,7 +25,6 @@ */ @CommandLine.Command( name = "zk", - mixinStandardHelpOptions = true, description = "Sub commands for working with ZooKeeper.", footer = "\nPass --help or -h after any COMMAND to see command-specific usage information.", subcommands = { @@ -40,6 +39,8 @@ }) public class ZkTool implements Callable { + @CommandLine.Mixin HelpMixin helpMixin; + @CommandLine.Spec CommandLine.Model.CommandSpec spec; @Override