4848import org .elasticsearch .core .Tuple ;
4949import org .elasticsearch .env .Environment ;
5050import org .elasticsearch .env .TestEnvironment ;
51+ import org .elasticsearch .plugin .scanner .NamedComponentScanner ;
5152import org .elasticsearch .plugins .Platforms ;
5253import org .elasticsearch .plugins .PluginDescriptor ;
5354import org .elasticsearch .plugins .PluginTestUtil ;
5455import org .elasticsearch .test .ESTestCase ;
5556import org .elasticsearch .test .PosixPermissionsResetter ;
57+ import org .elasticsearch .test .compiler .InMemoryJavaCompiler ;
58+ import org .elasticsearch .test .jar .JarUtils ;
5659import org .junit .After ;
5760import org .junit .Before ;
5861
8689import java .util .Arrays ;
8790import java .util .Date ;
8891import java .util .HashSet ;
92+ import java .util .LinkedHashMap ;
8993import java .util .List ;
9094import java .util .Locale ;
9195import java .util .Map ;
@@ -124,13 +128,16 @@ public class InstallPluginActionTests extends ESTestCase {
124128 private MockTerminal terminal ;
125129 private Tuple <Path , Environment > env ;
126130 private Path pluginDir ;
131+ private NamedComponentScanner namedComponentScanner ;
127132
128133 private final boolean isPosix ;
129134 private final boolean isReal ;
130135 private final String javaIoTmpdir ;
131136
132137 @ SuppressForbidden (reason = "sets java.io.tmpdir" )
133138 public InstallPluginActionTests (FileSystem fs , Function <String , Path > temp ) {
139+ assert "false" .equals (System .getProperty ("tests.security.manager" )) : "-Dtests.security.manager=false has to be set" ;
140+
134141 this .temp = temp ;
135142 this .isPosix = fs .supportedFileAttributeViews ().contains ("posix" );
136143 this .isReal = fs == PathUtils .getDefaultFileSystem ();
@@ -152,6 +159,7 @@ void jarHellCheck(PluginDescriptor candidateInfo, Path candidate, Path pluginsDi
152159 // no jarhell check
153160 }
154161 };
162+
155163 defaultAction = new InstallPluginAction (terminal , env .v2 (), false );
156164 }
157165
@@ -199,7 +207,9 @@ private static Configuration toPosix(Configuration configuration) {
199207 return configuration .toBuilder ().setAttributeViews ("basic" , "owner" , "posix" , "unix" ).build ();
200208 }
201209
202- /** Creates a test environment with bin, config and plugins directories. */
210+ /**
211+ * Creates a test environment with bin, config and plugins directories.
212+ */
203213 static Tuple <Path , Environment > createEnv (Function <String , Path > temp ) throws IOException {
204214 Path home = temp .apply ("install-plugin-command-tests" );
205215 Files .createDirectories (home .resolve ("bin" ));
@@ -216,7 +226,9 @@ static Path createPluginDir(Function<String, Path> temp) {
216226 return temp .apply ("pluginDir" );
217227 }
218228
219- /** creates a fake jar file with empty class files */
229+ /**
230+ * creates a fake jar file with empty class files
231+ */
220232 static void writeJar (Path jar , String ... classes ) throws IOException {
221233 try (ZipOutputStream stream = new ZipOutputStream (Files .newOutputStream (jar ))) {
222234 for (String clazz : classes ) {
@@ -237,13 +249,47 @@ static Path writeZip(Path structure, String prefix) throws IOException {
237249 return zip ;
238250 }
239251
240- /** creates a plugin .zip and returns the url for testing */
252+ /**
253+ * creates a plugin .zip and returns the url for testing
254+ */
241255 static InstallablePlugin createPluginZip (String name , Path structure , String ... additionalProps ) throws IOException {
242256 return createPlugin (name , structure , additionalProps );
243257 }
244258
259+ static void writeStablePlugin (String name , Path structure , boolean hasNamedComponentFile , String ... additionalProps )
260+ throws IOException {
261+ String [] properties = pluginProperties (name , additionalProps , true );
262+ PluginTestUtil .writeStablePluginProperties (structure , properties );
263+
264+ if (hasNamedComponentFile ) {
265+ PluginTestUtil .writeNamedComponentsFile (structure , namedComponentsJSON ());
266+ }
267+ Path jar = structure .resolve ("plugin.jar" );
268+
269+ JarUtils .createJarWithEntries (jar , Map .of ("p/A.class" , InMemoryJavaCompiler .compile ("p.A" , """
270+ package p;
271+ import org.elasticsearch.plugin.*;
272+ import org.elasticsearch.plugins.cli.test_model.*;
273+ @NamedComponent("a_component")
274+ public class A implements ExtensibleInterface{}
275+ """ ), "p/B.class" , InMemoryJavaCompiler .compile ("p.B" , """
276+ package p;
277+ import org.elasticsearch.plugin.*;
278+ import org.elasticsearch.plugins.cli.test_model.*;
279+ @NamedComponent("b_component")
280+ public class B implements ExtensibleInterface{}
281+ """ )));
282+ }
283+
245284 static void writePlugin (String name , Path structure , String ... additionalProps ) throws IOException {
246- String [] properties = Stream .concat (
285+ String [] properties = pluginProperties (name , additionalProps , false );
286+ PluginTestUtil .writePluginProperties (structure , properties );
287+ String className = name .substring (0 , 1 ).toUpperCase (Locale .ENGLISH ) + name .substring (1 ) + "Plugin" ;
288+ writeJar (structure .resolve ("plugin.jar" ), className );
289+ }
290+
291+ private static String [] pluginProperties (String name , String [] additionalProps , boolean isStable ) {
292+ return Stream .of (
247293 Stream .of (
248294 "description" ,
249295 "fake desc" ,
@@ -254,15 +300,12 @@ static void writePlugin(String name, Path structure, String... additionalProps)
254300 "elasticsearch.version" ,
255301 Version .CURRENT .toString (),
256302 "java.version" ,
257- System .getProperty ("java.specification.version" ),
258- "classname" ,
259- "FakePlugin"
303+ System .getProperty ("java.specification.version" )
304+
260305 ),
306+ isStable ? Stream .empty () : Stream .of ("classname" , "FakePlugin" ),
261307 Arrays .stream (additionalProps )
262- ).toArray (String []::new );
263- PluginTestUtil .writePluginProperties (structure , properties );
264- String className = name .substring (0 , 1 ).toUpperCase (Locale .ENGLISH ) + name .substring (1 ) + "Plugin" ;
265- writeJar (structure .resolve ("plugin.jar" ), className );
308+ ).flatMap (Function .identity ()).toArray (String []::new );
266309 }
267310
268311 static void writePluginSecurityPolicy (Path pluginDir , String ... permissions ) throws IOException {
@@ -276,6 +319,12 @@ static void writePluginSecurityPolicy(Path pluginDir, String... permissions) thr
276319 Files .write (pluginDir .resolve ("plugin-security.policy" ), securityPolicyContent .toString ().getBytes (StandardCharsets .UTF_8 ));
277320 }
278321
322+ static InstallablePlugin createStablePlugin (String name , Path structure , boolean hasNamedComponentFile , String ... additionalProps )
323+ throws IOException {
324+ writeStablePlugin (name , structure , hasNamedComponentFile , additionalProps );
325+ return new InstallablePlugin (name , writeZip (structure , null ).toUri ().toURL ().toString ());
326+ }
327+
279328 static InstallablePlugin createPlugin (String name , Path structure , String ... additionalProps ) throws IOException {
280329 writePlugin (name , structure , additionalProps );
281330 return new InstallablePlugin (name , writeZip (structure , null ).toUri ().toURL ().toString ());
@@ -310,6 +359,11 @@ void assertPlugin(String name, Path original, Environment environment) throws IO
310359 assertInstallCleaned (environment );
311360 }
312361
362+ void assertNamedComponentFile (String name , Path pluginDir , String expectedContent ) throws IOException {
363+ Path namedComponents = pluginDir .resolve (name ).resolve (PluginDescriptor .NAMED_COMPONENTS_FILENAME );
364+ assertThat (Files .readString (namedComponents ), equalTo (expectedContent ));
365+ }
366+
313367 void assertPluginInternal (String name , Path pluginsFile , Path originalPlugin ) throws IOException {
314368 Path got = pluginsFile .resolve (name );
315369 assertTrue ("dir " + name + " exists" , Files .exists (got ));
@@ -1507,4 +1561,42 @@ public void testInstallMigratedPlugins() throws Exception {
15071561 assertThat (terminal .getErrorOutput (), containsString ("[" + id + "] is no longer a plugin" ));
15081562 }
15091563 }
1564+
1565+ public void testStablePluginWithNamedComponentsFile () throws Exception {
1566+ InstallablePlugin stablePluginZip = createStablePlugin ("stable1" , pluginDir , true );
1567+ installPlugins (List .of (stablePluginZip ), env .v1 ());
1568+ assertPlugin ("stable1" , pluginDir , env .v2 ());
1569+ assertNamedComponentFile ("stable1" , env .v2 ().pluginsFile (), namedComponentsJSON ());
1570+ }
1571+
1572+ @ SuppressWarnings ("unchecked" )
1573+ public void testStablePluginWithoutNamedComponentsFile () throws Exception {
1574+ // named component will have to be generated upon install
1575+ InstallablePlugin stablePluginZip = createStablePlugin ("stable1" , pluginDir , false );
1576+
1577+ installPlugins (List .of (stablePluginZip ), env .v1 ());
1578+
1579+ assertPlugin ("stable1" , pluginDir , env .v2 ());
1580+ assertNamedComponentFile ("stable1" , env .v2 ().pluginsFile (), namedComponentsJSON ());
1581+ }
1582+
1583+ private Map <String , Map <String , String >> namedComponentsMap () {
1584+ Map <String , Map <String , String >> result = new LinkedHashMap <>();
1585+ Map <String , String > extensibles = new LinkedHashMap <>();
1586+ extensibles .put ("a_component" , "p.A" );
1587+ extensibles .put ("b_component" , "p.B" );
1588+ result .put ("org.elasticsearch.plugins.cli.test_model.ExtensibleInterface" , extensibles );
1589+ return result ;
1590+ }
1591+
1592+ private static String namedComponentsJSON () {
1593+ return """
1594+ {
1595+ "org.elasticsearch.plugins.cli.test_model.ExtensibleInterface": {
1596+ "a_component": "p.A",
1597+ "b_component": "p.B"
1598+ }
1599+ }
1600+ """ .replaceAll ("[\n \r \s ]" , "" );
1601+ }
15101602}
0 commit comments