Skip to content

Commit 1d9d6db

Browse files
authored
Introduce licensed plugins (#65490)
Backport of #64850. This PR introduces the concept of "licensed" plugins. Such plugins may only be installed on installations of the default distribution, and this is enforced by the plugin installer. This PR also moves the `quote-aware-fs` plugin to the `x-pack` directory, and marks it as licensed. Note that I didn't move the plugin source under `x-pack/plugin` because all the existing x-pack plugins are actually bundles as modules into the default distribution, whereas the `quota-aware-fs` plugin needs to remain a standalone plugin.
1 parent 04d7f6b commit 1d9d6db

File tree

27 files changed

+268
-206
lines changed

27 files changed

+268
-206
lines changed

buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginBuildPlugin.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class PluginBuildPlugin extends BuildPlugin {
4646
// this afterEvaluate must happen before the afterEvaluate added by integTest creation,
4747
// so that the file name resolution for installing the plugin will be setup
4848
project.afterEvaluate {
49-
boolean isXPackModule = project.path.startsWith(':x-pack:plugin')
49+
boolean isXPackModule = project.path.startsWith(':x-pack:plugin') || project.path.startsWith(':x-pack:quota-aware-fs')
5050
boolean isModule = project.path.startsWith(':modules:') || isXPackModule
5151
String name = project.pluginProperties.extension.name
5252
project.archivesBaseName = name

buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginPropertiesExtension.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ class PluginPropertiesExtension {
6464
*/
6565
private File noticeFile = null
6666

67+
/** True if the plugin is available under the Elastic license. */
68+
@Input
69+
boolean licensed = false
70+
6771
Project project = null
6872

6973
PluginPropertiesExtension(Project project) {

buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginPropertiesTask.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ class PluginPropertiesTask extends Copy {
8181
'classname': extension.classname,
8282
'extendedPlugins': extension.extendedPlugins.join(','),
8383
'hasNativeController': extension.hasNativeController,
84-
'requiresKeystore': extension.requiresKeystore
84+
'requiresKeystore': extension.requiresKeystore,
85+
'licensed': extension.licensed
8586
]
8687
}
8788
}

buildSrc/src/main/resources/plugin-descriptor.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,7 @@ extended.plugins=${extendedPlugins}
4343
#
4444
# 'has.native.controller': whether or not the plugin has a native controller
4545
has.native.controller=${hasNativeController}
46+
<% if (licensed) { %>
47+
# This plugin requires that a license agreement be accepted before installation
48+
licensed=${licensed}
49+
<% } %>

distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,9 @@ private void install(Terminal terminal, boolean isBatch, Path tmpRoot, Environme
801801
private void installPlugin(Terminal terminal, boolean isBatch, Path tmpRoot,
802802
Environment env, List<Path> deleteOnFailure) throws Exception {
803803
final PluginInfo info = loadPluginInfo(terminal, tmpRoot, env);
804+
805+
checkCanInstallationProceed(terminal, Build.CURRENT.flavor(), info);
806+
804807
// read optional security policy (extra permissions), if it exists, confirm or warn the user
805808
Path policy = tmpRoot.resolve(PluginInfo.ES_PLUGIN_POLICY);
806809
final Set<String> permissions;
@@ -950,4 +953,24 @@ public void close() throws IOException {
950953
IOUtils.rm(pathsToDeleteOnShutdown.toArray(new Path[pathsToDeleteOnShutdown.size()]));
951954
}
952955

956+
static void checkCanInstallationProceed(Terminal terminal, Build.Flavor flavor, PluginInfo info) throws Exception {
957+
if (info.isLicensed() == false) {
958+
return;
959+
}
960+
961+
if (flavor == Build.Flavor.DEFAULT) {
962+
return;
963+
}
964+
965+
Arrays.asList(
966+
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
967+
"@ ERROR: This is a licensed plugin @",
968+
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
969+
"",
970+
"This plugin is covered by the Elastic license, but this",
971+
"installation of Elasticsearch is: [" + flavor + "]."
972+
).forEach(terminal::println);
973+
974+
throw new UserException(ExitCodes.NOPERM, "Plugin license is incompatible with [" + flavor + "] installation");
975+
}
953976
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.plugins;
21+
22+
import static org.hamcrest.Matchers.containsString;
23+
import static org.hamcrest.Matchers.equalTo;
24+
25+
import java.util.Collections;
26+
27+
import org.elasticsearch.Build;
28+
import org.elasticsearch.Version;
29+
import org.elasticsearch.cli.ExitCodes;
30+
import org.elasticsearch.cli.MockTerminal;
31+
import org.elasticsearch.cli.UserException;
32+
import org.elasticsearch.test.ESTestCase;
33+
34+
public class InstallLicensedPluginTests extends ESTestCase {
35+
36+
/**
37+
* Check that an unlicensed plugin is accepted.
38+
*/
39+
public void testUnlicensedPlugin() throws Exception {
40+
MockTerminal terminal = new MockTerminal();
41+
PluginInfo pluginInfo = buildInfo(false);
42+
InstallPluginCommand.checkCanInstallationProceed(terminal, Build.Flavor.OSS, pluginInfo);
43+
}
44+
45+
/**
46+
* Check that a licensed plugin cannot be installed on OSS.
47+
*/
48+
public void testInstallPluginCommandOnOss() throws Exception {
49+
MockTerminal terminal = new MockTerminal();
50+
PluginInfo pluginInfo = buildInfo(true);
51+
final UserException userException = expectThrows(
52+
UserException.class,
53+
() -> InstallPluginCommand.checkCanInstallationProceed(terminal, Build.Flavor.OSS, pluginInfo)
54+
);
55+
56+
assertThat(userException.exitCode, equalTo(ExitCodes.NOPERM));
57+
assertThat(terminal.getOutput(), containsString("ERROR: This is a licensed plugin"));
58+
}
59+
60+
/**
61+
* Check that a licensed plugin cannot be installed when the distribution type is unknown.
62+
*/
63+
public void testInstallPluginCommandOnUnknownDistribution() throws Exception {
64+
MockTerminal terminal = new MockTerminal();
65+
PluginInfo pluginInfo = buildInfo(true);
66+
expectThrows(
67+
UserException.class,
68+
() -> InstallPluginCommand.checkCanInstallationProceed(terminal, Build.Flavor.UNKNOWN, pluginInfo)
69+
);
70+
assertThat(terminal.getOutput(), containsString("ERROR: This is a licensed plugin"));
71+
}
72+
73+
/**
74+
* Check that a licensed plugin can be installed when the distribution type is default.
75+
*/
76+
public void testInstallPluginCommandOnDefault() throws Exception {
77+
MockTerminal terminal = new MockTerminal();
78+
PluginInfo pluginInfo = buildInfo(true);
79+
InstallPluginCommand.checkCanInstallationProceed(terminal, Build.Flavor.DEFAULT, pluginInfo);
80+
}
81+
82+
private PluginInfo buildInfo(boolean isLicensed) {
83+
return new PluginInfo(
84+
"name",
85+
"description",
86+
"version",
87+
Version.CURRENT,
88+
"java version",
89+
"classname",
90+
Collections.emptyList(),
91+
false,
92+
isLicensed
93+
);
94+
}
95+
}

distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -829,14 +829,14 @@ public void testPluginAlreadyInstalled() throws Exception {
829829
"if you need to update the plugin, uninstall it first using command 'remove fake'"));
830830
}
831831

832-
private void installPlugin(MockTerminal terminal, boolean isBatch) throws Exception {
832+
private void installPlugin(MockTerminal terminal, boolean isBatch, String... additionalProperties) throws Exception {
833833
Tuple<Path, Environment> env = createEnv(fs, temp);
834834
Path pluginDir = createPluginDir(temp);
835835
// if batch is enabled, we also want to add a security policy
836836
if (isBatch) {
837837
writePluginSecurityPolicy(pluginDir, "setFactory");
838838
}
839-
String pluginZip = createPlugin("fake", pluginDir).toUri().toURL().toString();
839+
String pluginZip = createPlugin("fake", pluginDir, additionalProperties).toUri().toURL().toString();
840840
skipJarHellCommand.execute(terminal, pluginZip, isBatch, env.v2());
841841
}
842842

distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/ListPluginsCommandTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ public void testPluginWithVerbose() throws Exception {
152152
"Elasticsearch Version: " + Version.CURRENT.toString(),
153153
"Java Version: 1.8",
154154
"Native Controller: false",
155+
"Licensed: false",
155156
"Extended Plugins: []",
156157
" * Classname: org.fake"),
157158
terminal.getOutput());
@@ -172,6 +173,7 @@ public void testPluginWithNativeController() throws Exception {
172173
"Elasticsearch Version: " + Version.CURRENT.toString(),
173174
"Java Version: 1.8",
174175
"Native Controller: true",
176+
"Licensed: false",
175177
"Extended Plugins: []",
176178
" * Classname: org.fake"),
177179
terminal.getOutput());
@@ -193,6 +195,7 @@ public void testPluginWithVerboseMultiplePlugins() throws Exception {
193195
"Elasticsearch Version: " + Version.CURRENT.toString(),
194196
"Java Version: 1.8",
195197
"Native Controller: false",
198+
"Licensed: false",
196199
"Extended Plugins: []",
197200
" * Classname: org.fake",
198201
"fake_plugin2",
@@ -203,6 +206,7 @@ public void testPluginWithVerboseMultiplePlugins() throws Exception {
203206
"Elasticsearch Version: " + Version.CURRENT.toString(),
204207
"Java Version: 1.8",
205208
"Native Controller: false",
209+
"Licensed: false",
206210
"Extended Plugins: []",
207211
" * Classname: org.fake2"),
208212
terminal.getOutput());

docs/build.gradle

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,8 @@ integTestCluster {
7171
project.rootProject.subprojects.findAll { it.parent.path == ':plugins' }.each { subproj ->
7272
/* Skip repositories. We just aren't going to be able to test them so it
7373
* doesn't make sense to waste time installing them.
74-
* Also skip quota-aware-fs since it has to be configured in order to use
75-
* it, otherwise ES will not start. */
76-
if (subproj.path.startsWith(':plugins:repository-') || subproj.path.startsWith(':plugins:quota-aware-fs')) {
74+
*/
75+
if (subproj.path.startsWith(':plugins:repository-')) {
7776
return
7877
}
7978
subproj.afterEvaluate { // need to wait until the project has been configured

qa/smoke-test-plugins/build.gradle

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ apply plugin: 'elasticsearch.rest-test'
2424

2525
ext.pluginsCount = 0
2626
project(':plugins').getChildProjects().each { pluginName, pluginProject ->
27-
if (pluginName == 'quota-aware-fs') {
28-
// This plugin has to be configured to work via system properties
29-
return
30-
}
3127
integTestCluster {
3228
plugin pluginProject.path
3329
}

0 commit comments

Comments
 (0)