2626import org .elasticsearch .cli .ExitCodes ;
2727import org .elasticsearch .cli .Terminal ;
2828import org .elasticsearch .cli .UserException ;
29- import org .elasticsearch .common .Strings ;
3029import org .elasticsearch .env .Environment ;
3130
3231import java .io .IOException ;
3332import java .nio .file .FileAlreadyExistsException ;
3433import java .nio .file .Files ;
3534import java .nio .file .Path ;
3635import java .util .ArrayList ;
36+ import java .util .Arrays ;
3737import java .util .List ;
3838import java .util .Locale ;
3939import java .util .stream .Collectors ;
4646 */
4747class RemovePluginCommand extends EnvironmentAwareCommand {
4848
49+ private final OptionSpec <Void > purgeOption ;
4950 private final OptionSpec <String > arguments ;
5051
5152 RemovePluginCommand () {
5253 super ("removes a plugin from Elasticsearch" );
54+ this .purgeOption = parser .acceptsAll (Arrays .asList ("p" , "purge" ), "Purge plugin configuration files" );
5355 this .arguments = parser .nonOptions ("plugin name" );
5456 }
5557
5658 @ Override
57- protected void execute (final Terminal terminal , final OptionSet options , final Environment env )
58- throws Exception {
59+ protected void execute (final Terminal terminal , final OptionSet options , final Environment env ) throws Exception {
5960 final String pluginName = arguments .value (options );
60- execute (terminal , pluginName , env );
61+ final boolean purge = options .has (purgeOption );
62+ execute (terminal , env , pluginName , purge );
6163 }
6264
6365 /**
6466 * Remove the plugin specified by {@code pluginName}.
6567 *
6668 * @param terminal the terminal to use for input/output
67- * @param pluginName the name of the plugin to remove
6869 * @param env the environment for the local node
70+ * @param pluginName the name of the plugin to remove
71+ * @param purge if true, plugin configuration files will be removed but otherwise preserved
6972 * @throws IOException if any I/O exception occurs while performing a file operation
7073 * @throws UserException if plugin name is null
7174 * @throws UserException if plugin directory does not exist
7275 * @throws UserException if the plugin bin directory is not a directory
7376 */
74- void execute (final Terminal terminal , final String pluginName , final Environment env )
75- throws IOException , UserException {
77+ void execute (
78+ final Terminal terminal ,
79+ final Environment env ,
80+ final String pluginName ,
81+ final boolean purge ) throws IOException , UserException {
7682 if (pluginName == null ) {
7783 throw new UserException (ExitCodes .USAGE , "plugin name is required" );
7884 }
7985
80- terminal .println ("-> removing [" + Strings . coalesceToEmpty ( pluginName ) + "]..." );
86+ terminal .println ("-> removing [" + pluginName + "]..." );
8187
8288 final Path pluginDir = env .pluginsFile ().resolve (pluginName );
83- if (Files .exists (pluginDir ) == false ) {
89+ final Path pluginConfigDir = env .configFile ().resolve (pluginName );
90+ final Path removing = env .pluginsFile ().resolve (".removing-" + pluginName );
91+ /*
92+ * If the plugin does not exist and the plugin config does not exist, fail to the user that the plugin is not found, unless there's
93+ * a marker file left from a previously failed attempt in which case we proceed to clean up the marker file. Or, if the plugin does
94+ * not exist, the plugin config does, and we are not purging, again fail to the user that the plugin is not found.
95+ */
96+ if ((!Files .exists (pluginDir ) && !Files .exists (pluginConfigDir ) && !Files .exists (removing ))
97+ || (!Files .exists (pluginDir ) && Files .exists (pluginConfigDir ) && !purge )) {
8498 final String message = String .format (
85- Locale .ROOT ,
86- "plugin [%s] not found; "
87- + "run 'elasticsearch-plugin list' to get list of installed plugins" ,
88- pluginName );
99+ Locale .ROOT , "plugin [%s] not found; run 'elasticsearch-plugin list' to get list of installed plugins" , pluginName );
89100 throw new UserException (ExitCodes .CONFIG , message );
90101 }
91102
92103 final List <Path > pluginPaths = new ArrayList <>();
93104
105+ /*
106+ * Add the contents of the plugin directory before creating the marker file and adding it to the list of paths to be deleted so
107+ * that the marker file is the last file to be deleted.
108+ */
109+ if (Files .exists (pluginDir )) {
110+ try (Stream <Path > paths = Files .list (pluginDir )) {
111+ pluginPaths .addAll (paths .collect (Collectors .toList ()));
112+ }
113+ terminal .println (VERBOSE , "removing [" + pluginDir + "]" );
114+ }
115+
94116 final Path pluginBinDir = env .binFile ().resolve (pluginName );
95117 if (Files .exists (pluginBinDir )) {
96- if (Files .isDirectory (pluginBinDir ) == false ) {
97- throw new UserException (
98- ExitCodes .IO_ERROR , "bin dir for " + pluginName + " is not a directory" );
118+ if (!Files .isDirectory (pluginBinDir )) {
119+ throw new UserException (ExitCodes .IO_ERROR , "bin dir for " + pluginName + " is not a directory" );
120+ }
121+ try (Stream <Path > paths = Files .list (pluginBinDir )) {
122+ pluginPaths .addAll (paths .collect (Collectors .toList ()));
99123 }
100124 pluginPaths .add (pluginBinDir );
101125 terminal .println (VERBOSE , "removing [" + pluginBinDir + "]" );
102126 }
103127
104- terminal .println (VERBOSE , "removing [" + pluginDir + "]" );
105- /*
128+ if (Files .exists (pluginConfigDir )) {
129+ if (purge ) {
130+ try (Stream <Path > paths = Files .list (pluginConfigDir )) {
131+ pluginPaths .addAll (paths .collect (Collectors .toList ()));
132+ }
133+ pluginPaths .add (pluginConfigDir );
134+ terminal .println (VERBOSE , "removing [" + pluginConfigDir + "]" );
135+ } else {
136+ /*
137+ * By default we preserve the config files in case the user is upgrading the plugin, but we print a message so the user
138+ * knows in case they want to remove manually.
139+ */
140+ final String message = String .format (
141+ Locale .ROOT ,
142+ "-> preserving plugin config files [%s] in case of upgrade; use --purge if not needed" ,
143+ pluginConfigDir );
144+ terminal .println (message );
145+ }
146+ }
147+
148+ /*
106149 * We are going to create a marker file in the plugin directory that indicates that this plugin is a state of removal. If the
107150 * removal fails, the existence of this marker file indicates that the plugin is in a garbage state. We check for existence of this
108- * marker file during startup so that we do not startup with plugins in such a garbage state.
151+ * marker file during startup so that we do not startup with plugins in such a garbage state. Up to this point, we have not done
152+ * anything destructive, so we create the marker file as the last action before executing destructive operations. We place this
153+ * marker file in the root plugin directory (not the specific plugin directory) so that we do not have to create the specific plugin
154+ * directory if it does not exist (we are purging configuration files).
109155 */
110- final Path removing = pluginDir .resolve (".removing-" + pluginName );
111- /*
112- * Add the contents of the plugin directory before creating the marker file and adding it to the list of paths to be deleted so
113- * that the marker file is the last file to be deleted.
114- */
115- try (Stream <Path > paths = Files .list (pluginDir )) {
116- pluginPaths .addAll (paths .collect (Collectors .toList ()));
117- }
118156 try {
119157 Files .createFile (removing );
120158 } catch (final FileAlreadyExistsException e ) {
@@ -124,24 +162,14 @@ void execute(final Terminal terminal, final String pluginName, final Environment
124162 */
125163 terminal .println (VERBOSE , "marker file [" + removing + "] already exists" );
126164 }
127- // now add the marker file
128- pluginPaths .add (removing );
129- // finally, add the plugin directory
165+
166+ // add the plugin directory
130167 pluginPaths .add (pluginDir );
131- IOUtils .rm (pluginPaths .toArray (new Path [pluginPaths .size ()]));
132168
133- /*
134- * We preserve the config files in case the user is upgrading the plugin, but we print a
135- * message so the user knows in case they want to remove manually.
136- */
137- final Path pluginConfigDir = env .configFile ().resolve (pluginName );
138- if (Files .exists (pluginConfigDir )) {
139- final String message = String .format (
140- Locale .ROOT ,
141- "-> preserving plugin config files [%s] in case of upgrade; delete manually if not needed" ,
142- pluginConfigDir );
143- terminal .println (message );
144- }
169+ // finally, add the marker file
170+ pluginPaths .add (removing );
171+
172+ IOUtils .rm (pluginPaths .toArray (new Path [pluginPaths .size ()]));
145173 }
146174
147175}
0 commit comments