Add did-you-mean for plugin cli

This commit adds error messages like: `Unknown plugin xpack, did you mean [x-pack]?`

Closes #18896
This commit is contained in:
Jim Ferenczi 2016-06-17 12:17:48 +02:00
parent 5284c5094d
commit 529c2ca13f
2 changed files with 40 additions and 1 deletions

View File

@ -45,11 +45,14 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import joptsimple.OptionSpec; import joptsimple.OptionSpec;
import org.apache.lucene.search.spell.LevensteinDistance;
import org.apache.lucene.util.CollectionUtil;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.bootstrap.JarHell; import org.elasticsearch.bootstrap.JarHell;
@ -57,6 +60,7 @@ import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.SettingCommand; import org.elasticsearch.cli.SettingCommand;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserError; import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.hash.MessageDigests; import org.elasticsearch.common.hash.MessageDigests;
import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.common.io.FileSystemUtils;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
@ -239,12 +243,31 @@ class InstallPluginCommand extends SettingCommand {
// fall back to plain old URL // fall back to plain old URL
if (pluginId.contains(":/") == false) { if (pluginId.contains(":/") == false) {
// definitely not a valid url, so assume it is a plugin name // definitely not a valid url, so assume it is a plugin name
throw new UserError(ExitCodes.USAGE, "Unknown plugin " + pluginId); List<String> plugins = checkMisspelledPlugin(pluginId);
String msg = "Unknown plugin " + pluginId;
if (plugins.isEmpty() == false) {
msg += ", did you mean " + (plugins.size() == 1 ? "[" + plugins.get(0) + "]": "any of " + plugins.toString()) + "?";
}
throw new UserError(ExitCodes.USAGE, msg);
} }
terminal.println("-> Downloading " + URLDecoder.decode(pluginId, "UTF-8")); terminal.println("-> Downloading " + URLDecoder.decode(pluginId, "UTF-8"));
return downloadZip(terminal, pluginId, tmpDir); return downloadZip(terminal, pluginId, tmpDir);
} }
/** Returns all the official plugin names that look similar to pluginId. **/
private List<String> checkMisspelledPlugin(String pluginId) {
LevensteinDistance ld = new LevensteinDistance();
List<Tuple<Float, String>> scoredKeys = new ArrayList<>();
for (String officialPlugin : OFFICIAL_PLUGINS) {
float distance = ld.getDistance(pluginId, officialPlugin);
if (distance > 0.7f) {
scoredKeys.add(new Tuple<>(distance, officialPlugin));
}
}
CollectionUtil.timSort(scoredKeys, (a, b) -> b.v1().compareTo(a.v1()));
return scoredKeys.stream().map((a) -> a.v2()).collect(Collectors.toList());
}
/** Downloads a zip from the url, into a temp file under the given temp dir. */ /** Downloads a zip from the url, into a temp file under the given temp dir. */
private Path downloadZip(Terminal terminal, String urlString, Path tmpDir) throws IOException { private Path downloadZip(Terminal terminal, String urlString, Path tmpDir) throws IOException {
terminal.println(VERBOSE, "Retrieving zip from " + urlString); terminal.println(VERBOSE, "Retrieving zip from " + urlString);

View File

@ -67,6 +67,7 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
@ -567,6 +568,21 @@ public class InstallPluginCommandTests extends ESTestCase {
assertTrue(terminal.getOutput(), terminal.getOutput().contains("x-pack")); assertTrue(terminal.getOutput(), terminal.getOutput().contains("x-pack"));
} }
public void testInstallMisspelledOfficialPlugins() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
UserError e = expectThrows(UserError.class, () -> installPlugin("xpack", env.v1()));
assertThat(e.getMessage(), containsString("Unknown plugin xpack, did you mean [x-pack]?"));
e = expectThrows(UserError.class, () -> installPlugin("analysis-smartnc", env.v1()));
assertThat(e.getMessage(), containsString("Unknown plugin analysis-smartnc, did you mean [analysis-smartcn]?"));
e = expectThrows(UserError.class, () -> installPlugin("repository", env.v1()));
assertThat(e.getMessage(), containsString("Unknown plugin repository, did you mean any of [repository-s3, repository-gcs]?"));
e = expectThrows(UserError.class, () -> installPlugin("unknown_plugin", env.v1()));
assertThat(e.getMessage(), containsString("Unknown plugin unknown_plugin"));
}
// TODO: test batch flag? // TODO: test batch flag?
// TODO: test checksum (need maven/official below) // TODO: test checksum (need maven/official below)
// TODO: test maven, official, and staging install...need tests with fixtures... // TODO: test maven, official, and staging install...need tests with fixtures...