blob: 89aa244945e58259b3656ed96248fefd21d15c5f [file] [log] [blame]
/*
* 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.util;
import java.io.File;
import java.lang.invoke.MethodHandles;
import java.nio.file.Paths;
import java.util.Map;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.io.FileUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.lucene.util.SuppressForbidden;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.util.Pair;
import org.apache.solr.packagemanager.PackageManager;
import org.apache.solr.packagemanager.PackageUtils;
import org.apache.solr.packagemanager.RepositoryManager;
import org.apache.solr.packagemanager.SolrPackage;
import org.apache.solr.packagemanager.SolrPackage.SolrPackageRelease;
import org.apache.solr.packagemanager.SolrPackageInstance;
import org.apache.solr.util.SolrCLI.StatusTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.packagemanager.PackageUtils.print;
import static org.apache.solr.packagemanager.PackageUtils.printGreen;
public class PackageTool extends SolrCLI.ToolBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@SuppressForbidden(reason = "Need to turn off logging, and SLF4J doesn't seem to provide for a way.")
public PackageTool() {
// Need a logging free, clean output going through to the user.
Configurator.setRootLevel(Level.OFF);
}
@Override
public String getName() {
return "package";
}
public static String solrUrl = null;
public static String solrBaseUrl = null;
public PackageManager packageManager;
public RepositoryManager repositoryManager;
@Override
@SuppressForbidden(reason = "We really need to print the stacktrace here, otherwise "
+ "there shall be little else information to debug problems. Other SolrCLI tools "
+ "don't print stack traces, hence special treatment is needed here.")
protected void runImpl(CommandLine cli) throws Exception {
try {
solrUrl = cli.getOptionValues("solrUrl")[cli.getOptionValues("solrUrl").length-1];
solrBaseUrl = solrUrl.replaceAll("\\/solr$", ""); // strip out ending "/solr"
log.info("Solr url:{}, solr base url: {}", solrUrl, solrBaseUrl);
String zkHost = getZkHost(cli);
log.info("ZK: {}", zkHost);
String cmd = cli.getArgList().size() == 0? "help": cli.getArgs()[0];
try (HttpSolrClient solrClient = new HttpSolrClient.Builder(solrBaseUrl).build()) {
if (cmd != null) {
packageManager = new PackageManager(solrClient, solrBaseUrl, zkHost);
try {
repositoryManager = new RepositoryManager(solrClient, packageManager);
switch (cmd) {
case "add-repo":
String repoName = cli.getArgs()[1];
String repoUrl = cli.getArgs()[2];
repositoryManager.addRepository(repoName, repoUrl);
PackageUtils.printGreen("Added repository: " + repoName);
break;
case "add-key":
String keyFilename = cli.getArgs()[1];
repositoryManager.addKey(FileUtils.readFileToByteArray(new File(keyFilename)), Paths.get(keyFilename).getFileName().toString());
break;
case "list-installed":
PackageUtils.printGreen("Installed packages:\n-----");
for (SolrPackageInstance pkg: packageManager.fetchInstalledPackageInstances()) {
PackageUtils.printGreen(pkg);
}
break;
case "list-available":
PackageUtils.printGreen("Available packages:\n-----");
for (SolrPackage pkg: repositoryManager.getPackages()) {
PackageUtils.printGreen(pkg.name + " \t\t"+pkg.description);
for (SolrPackageRelease version: pkg.versions) {
PackageUtils.printGreen("\tVersion: "+version.version);
}
}
break;
case "list-deployed":
if (cli.hasOption('c')) {
String collection = cli.getArgs()[1];
Map<String, SolrPackageInstance> packages = packageManager.getPackagesDeployed(collection);
PackageUtils.printGreen("Packages deployed on " + collection + ":");
for (String packageName: packages.keySet()) {
PackageUtils.printGreen("\t" + packages.get(packageName));
}
} else {
String packageName = cli.getArgs()[1];
Map<String, String> deployedCollections = packageManager.getDeployedCollections(packageName);
if (deployedCollections.isEmpty() == false) {
PackageUtils.printGreen("Collections on which package " + packageName + " was deployed:");
for (String collection: deployedCollections.keySet()) {
PackageUtils.printGreen("\t" + collection + "("+packageName+":"+deployedCollections.get(collection)+")");
}
} else {
PackageUtils.printGreen("Package "+packageName+" not deployed on any collection.");
}
}
break;
case "install":
{
Pair<String, String> parsedVersion = parsePackageVersion(cli.getArgList().get(1).toString());
String packageName = parsedVersion.first();
String version = parsedVersion.second();
boolean success = repositoryManager.install(packageName, version);
if (success) {
PackageUtils.printGreen(packageName + " installed.");
} else {
PackageUtils.printRed(packageName + " installation failed.");
}
break;
}
case "deploy":
{
if (cli.hasOption("cluster") || cli.hasOption("collections")) {
Pair<String, String> parsedVersion = parsePackageVersion(cli.getArgList().get(1).toString());
String packageName = parsedVersion.first();
String version = parsedVersion.second();
boolean noprompt = cli.hasOption('y');
boolean isUpdate = cli.hasOption("update") || cli.hasOption('u');
String collections[] = cli.hasOption("collections")? PackageUtils.validateCollections(cli.getOptionValue("collections").split(",")): new String[] {};
packageManager.deploy(packageName, version, collections, cli.hasOption("cluster"), cli.getOptionValues("param"), isUpdate, noprompt);
} else {
PackageUtils.printRed("Either specify -cluster to deploy cluster level plugins or -collections <list-of-collections> to deploy collection level plugins");
}
break;
}
case "undeploy":
{
if (cli.hasOption("cluster") || cli.hasOption("collections")) {
Pair<String, String> parsedVersion = parsePackageVersion(cli.getArgList().get(1).toString());
if (parsedVersion.second() != null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Only package name expected, without a version. Actual: " + cli.getArgList().get(1));
}
String packageName = parsedVersion.first();
String collections[] = cli.hasOption("collections")? PackageUtils.validateCollections(cli.getOptionValue("collections").split(",")): new String[] {};
packageManager.undeploy(packageName, collections, cli.hasOption("cluster"));
} else {
PackageUtils.printRed("Either specify -cluster to undeploy cluster level plugins or -collections <list-of-collections> to undeploy collection level plugins");
}
break;
}
case "uninstall": {
Pair<String, String> parsedVersion = parsePackageVersion(cli.getArgList().get(1).toString());
if (parsedVersion.second() == null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Package name and version are both required. Actual: " + cli.getArgList().get(1));
}
String packageName = parsedVersion.first();
String version = parsedVersion.second();
packageManager.uninstall(packageName, version);
break;
}
case "help":
case "usage":
print("Package Manager\n---------------");
printGreen("./solr package add-repo <repository-name> <repository-url>");
print("Add a repository to Solr.");
print("");
printGreen("./solr package add-key <file-containing-trusted-key>");
print("Add a trusted key to Solr.");
print("");
printGreen("./solr package install <package-name>[:<version>] ");
print("Install a package into Solr. This copies over the artifacts from the repository into Solr's internal package store and sets up classloader for this package to be used.");
print("");
printGreen("./solr package deploy <package-name>[:<version>] [-y] [--update] -collections <comma-separated-collections> [-p <param1>=<val1> -p <param2>=<val2> ...] ");
print("Bootstraps a previously installed package into the specified collections. It the package accepts parameters for its setup commands, they can be specified (as per package documentation).");
print("");
printGreen("./solr package list-installed");
print("Print a list of packages installed in Solr.");
print("");
printGreen("./solr package list-available");
print("Print a list of packages available in the repositories.");
print("");
printGreen("./solr package list-deployed -c <collection>");
print("Print a list of packages deployed on a given collection.");
print("");
printGreen("./solr package list-deployed <package-name>");
print("Print a list of collections on which a given package has been deployed.");
print("");
printGreen("./solr package undeploy <package-name> -collections <comma-separated-collections>");
print("Undeploys a package from specified collection(s)");
print("");
printGreen("./solr package uninstall <package-name>:<version>");
print("Uninstall an unused package with specified version from Solr. Both package name and version are required.");
print("\n");
print("Note: (a) Please add '-solrUrl http://host:port' parameter if needed (usually on Windows).");
print(" (b) Please make sure that all Solr nodes are started with '-Denable.packages=true' parameter.");
print("\n");
break;
default:
throw new RuntimeException("Unrecognized command: "+cmd);
};
} finally {
packageManager.close();
}
}
}
log.info("Finished: {}", cmd);
} catch (Exception ex) {
ex.printStackTrace(); // We need to print this since SolrCLI drops the stack trace in favour of brevity. Package tool should surely print full stacktraces!
throw ex;
}
}
/**
* Parses package name and version in the format "name:version" or "name"
* @return A pair of package name (first) and version (second)
*/
private Pair<String, String> parsePackageVersion(String arg) {
String[] splits = arg.split(":");
if (splits.length > 2) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Invalid package name: " + arg +
". Didn't match the pattern: <packagename>:<version> or <packagename>");
}
String packageName = splits[0];
String version = splits.length == 2? splits[1]: null;
return new Pair<>(packageName, version);
}
public Option[] getOptions() {
return new Option[] {
Option.builder("solrUrl")
.argName("URL")
.hasArg()
.required(true)
.desc("Address of the Solr Web application, defaults to: " + SolrCLI.DEFAULT_SOLR_URL + '.')
.build(),
Option.builder("collections")
.argName("COLLECTIONS")
.hasArg()
.required(false)
.desc("Specifies that this action should affect plugins for the given collections only, excluding cluster level plugins.")
.build(),
Option.builder("cluster")
.required(false)
.desc("Specifies that this action should affect cluster-level plugins only.")
.build(),
Option.builder("p")
.argName("PARAMS")
.hasArgs()
.required(false)
.desc("List of parameters to be used with deploy command.")
.longOpt("param")
.build(),
Option.builder("u")
.required(false)
.desc("If a deployment is an update over a previous deployment.")
.longOpt("update")
.build(),
Option.builder("c")
.required(false)
.desc("The collection to apply the package to, not required.")
.longOpt("collection")
.build(),
Option.builder("y")
.required(false)
.desc("Don't prompt for input; accept all default choices, defaults to false.")
.longOpt("noprompt")
.build()
};
}
private String getZkHost(CommandLine cli) throws Exception {
String zkHost = cli.getOptionValue("zkHost");
if (zkHost != null)
return zkHost;
String systemInfoUrl = solrUrl+"/admin/info/system";
CloseableHttpClient httpClient = SolrCLI.getHttpClient();
try {
// hit Solr to get system info
Map<String,Object> systemInfo = SolrCLI.getJson(httpClient, systemInfoUrl, 2, true);
// convert raw JSON into user-friendly output
StatusTool statusTool = new StatusTool();
Map<String,Object> status = statusTool.reportStatus(solrUrl+"/", systemInfo, httpClient);
@SuppressWarnings({"unchecked"})
Map<String,Object> cloud = (Map<String, Object>)status.get("cloud");
if (cloud != null) {
String zookeeper = (String) cloud.get("ZooKeeper");
if (zookeeper.endsWith("(embedded)")) {
zookeeper = zookeeper.substring(0, zookeeper.length() - "(embedded)".length());
}
zkHost = zookeeper;
}
} finally {
HttpClientUtil.close(httpClient);
}
return zkHost;
}
}