diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/SampleResourcePluginIT.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/SampleResourcePluginIT.java new file mode 100644 index 0000000000000..2eb30b2d3d2cd --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/SampleResourcePluginIT.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource; + +public class SampleResourcePluginIT {} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResource.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResource.java new file mode 100644 index 0000000000000..25c03fd1c1878 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResource.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.plugins.resource.SharableResource; + +import java.io.IOException; +import java.time.Instant; + +public class SampleResource implements SharableResource { + + private String name; + private Instant lastUpdateTime; + + public SampleResource() { + Instant now = Instant.now(); + this.lastUpdateTime = now; + } + + public SampleResource(StreamInput in) throws IOException { + this.name = in.readString(); + this.lastUpdateTime = in.readInstant(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("name", name).endObject(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(name); + out.writeInstant(lastUpdateTime); + + } + + @Override + public String getWriteableName() { + return "sample_resource"; + } + + @Override + public String getName() { + return name; + } + + @Override + public Instant getLastUpdateTime() { + return lastUpdateTime; + } + + public void setName(String name) { + this.name = name; + } + + public void setLastUpdateTime(Instant lastUpdateTime) { + this.lastUpdateTime = lastUpdateTime; + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourceParser.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourceParser.java new file mode 100644 index 0000000000000..d674aacd97103 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourceParser.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample; + +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.core.xcontent.XContentParserUtils; +import org.opensearch.plugins.resource.ResourceParser; + +import java.io.IOException; +import java.time.Instant; + +public class SampleResourceParser implements ResourceParser { + + @Override + public SampleResource parse(XContentParser parser, String id) throws IOException { + SampleResource resource = new SampleResource(); + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); + + while (!parser.nextToken().equals(XContentParser.Token.END_OBJECT)) { + String fieldName = parser.currentName(); + parser.nextToken(); + switch (fieldName) { + case "name": + resource.setName(parser.text()); + break; + case "last_update_time": + resource.setLastUpdateTime(parseInstantValue(parser)); + break; + default: + XContentParserUtils.throwUnknownToken(parser.currentToken(), parser.getTokenLocation()); + } + } + return resource; + } + + private Instant parseInstantValue(XContentParser parser) throws IOException { + if (XContentParser.Token.VALUE_NULL.equals(parser.currentToken())) { + return null; + } + if (parser.currentToken().isValue()) { + return Instant.ofEpochMilli(parser.longValue()); + } + XContentParserUtils.throwUnknownToken(parser.currentToken(), parser.getTokenLocation()); + return null; + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourcePlugin.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourcePlugin.java new file mode 100644 index 0000000000000..1f7cb3b183e35 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourcePlugin.java @@ -0,0 +1,74 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample; + +import org.opensearch.action.ActionRequest; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.IndexScopedSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsFilter; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.indices.SystemIndexDescriptor; +import org.opensearch.plugins.ActionPlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.ResourcePlugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.plugins.resource.SharableResourceType; +import org.opensearch.plugins.resource.sample.action.create.CreateSampleResourceAction; +import org.opensearch.plugins.resource.sample.action.create.CreateSampleResourceRestAction; +import org.opensearch.plugins.resource.sample.action.create.CreateSampleResourceTransportAction; +import org.opensearch.plugins.resource.sample.action.get.GetSampleResourceAction; +import org.opensearch.plugins.resource.sample.action.get.GetSampleResourceRestAction; +import org.opensearch.plugins.resource.sample.action.get.GetSampleResourceTransportAction; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestHandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +public class SampleResourcePlugin extends Plugin implements ResourcePlugin, SystemIndexPlugin, ActionPlugin { + + public static final String RESOURCE_INDEX_NAME = ".sample_resources"; + + @Override + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + return List.of(new CreateSampleResourceRestAction(), new GetSampleResourceRestAction()); + } + + @Override + public List> getActions() { + return List.of( + new ActionHandler<>(CreateSampleResourceAction.INSTANCE, CreateSampleResourceTransportAction.class), + new ActionHandler<>(GetSampleResourceAction.INSTANCE, GetSampleResourceTransportAction.class) + ); + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Example index with resources"); + return Collections.singletonList(systemIndexDescriptor); + } + + @Override + public List getResourceTypes() { + return List.of(SampleResourceType.getInstance()); + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourceType.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourceType.java new file mode 100644 index 0000000000000..d67dd825974d1 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/SampleResourceType.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample; + +import org.opensearch.plugins.resource.ResourceSharingService; +import org.opensearch.plugins.resource.SharableResourceType; + +import static org.opensearch.plugins.resource.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +public class SampleResourceType implements SharableResourceType { + private volatile ResourceSharingService resourceSharingService; + + private static final SampleResourceType INSTANCE = new SampleResourceType(); + + private SampleResourceType() {} + + public static SampleResourceType getInstance() { + return INSTANCE; + } + + @Override + public String getResourceType() { + return "sample_resource"; + } + + @Override + public String getResourceIndex() { + return RESOURCE_INDEX_NAME; + } + + @Override + public void assignResourceSharingService(ResourceSharingService resourceSharingService) { + this.resourceSharingService = resourceSharingService; + } + + public ResourceSharingService getResourceSharingService() { + return this.resourceSharingService; + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceAction.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceAction.java new file mode 100644 index 0000000000000..2384719bee159 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceAction.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample.action.create; + +import org.opensearch.action.ActionType; + +/** + * Action to create a sample resource + */ +public class CreateSampleResourceAction extends ActionType { + /** + * Create sample resource action instance + */ + public static final CreateSampleResourceAction INSTANCE = new CreateSampleResourceAction(); + /** + * Create sample resource action name + */ + public static final String NAME = "cluster:admin/sampleresource/create"; + + private CreateSampleResourceAction() { + super(NAME, CreateSampleResourceResponse::new); + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceRequest.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceRequest.java new file mode 100644 index 0000000000000..33390a973eebc --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceRequest.java @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample.action.create; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.plugins.resource.SharableResource; +import org.opensearch.plugins.resource.sample.SampleResource; + +import java.io.IOException; + +/** + * Request object for CreateSampleResource transport action + */ +public class CreateSampleResourceRequest extends ActionRequest { + + private final SampleResource resource; + + /** + * Default constructor + */ + public CreateSampleResourceRequest(SampleResource resource) { + this.resource = resource; + } + + public CreateSampleResourceRequest(StreamInput in, Reader resourceReader) throws IOException { + this.resource = resourceReader.read(in); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + resource.writeTo(out); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public SharableResource getResource() { + return this.resource; + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceResponse.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceResponse.java new file mode 100644 index 0000000000000..ffc6d0e4dd73b --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceResponse.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample.action.create; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +import java.io.IOException; + +/** + * Response to a CreateSampleResourceRequest + */ +public class CreateSampleResourceResponse extends ActionResponse implements ToXContentObject { + private final String resourceId; + + /** + * Default constructor + * + * @param resourceId The resourceId + */ + public CreateSampleResourceResponse(String resourceId) { + this.resourceId = resourceId; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(resourceId); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public CreateSampleResourceResponse(final StreamInput in) throws IOException { + resourceId = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("resourceId", resourceId); + builder.endObject(); + return builder; + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceRestAction.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceRestAction.java new file mode 100644 index 0000000000000..b0242e8eaf459 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceRestAction.java @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample.action.create; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.plugins.resource.sample.SampleResource; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.POST; + +public class CreateSampleResourceRestAction extends BaseRestHandler { + + public CreateSampleResourceRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(POST, "/_plugins/resource_sharing_example/resource")); + } + + @Override + public String getName() { + return "create_sample_resource"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String name = (String) source.get("name"); + SampleResource resource = new SampleResource(); + resource.setName(name); + final CreateSampleResourceRequest createSampleResourceRequest = new CreateSampleResourceRequest<>(resource); + return channel -> client.executeLocally( + CreateSampleResourceAction.INSTANCE, + createSampleResourceRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceTransportAction.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceTransportAction.java new file mode 100644 index 0000000000000..634ac087b826e --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/create/CreateSampleResourceTransportAction.java @@ -0,0 +1,89 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample.action.create; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.admin.indices.create.CreateIndexResponse; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.plugins.resource.SharableResource; +import org.opensearch.plugins.resource.sample.SampleResource; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import java.io.IOException; + +import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; + +/** + * Transport action for CreateSampleResource. + */ +public class CreateSampleResourceTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(CreateSampleResourceTransportAction.class); + + private final TransportService transportService; + private final Client nodeClient; + private final String resourceIndex; + + public CreateSampleResourceTransportAction( + TransportService transportService, + ActionFilters actionFilters, + Client nodeClient, + String actionName, + String resourceIndex, + Writeable.Reader resourceReader + ) { + super(actionName, transportService, actionFilters, (in) -> new CreateSampleResourceRequest(in, resourceReader)); + this.transportService = transportService; + this.nodeClient = nodeClient; + this.resourceIndex = resourceIndex; + } + + @Override + protected void doExecute(Task task, CreateSampleResourceRequest request, ActionListener listener) { + try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { + CreateIndexRequest cir = new CreateIndexRequest(resourceIndex); + ActionListener cirListener = ActionListener.wrap( + response -> { createResource(request, listener); }, + (failResponse) -> { + /* Index already exists, ignore and continue */ + createResource(request, listener); + } + ); + nodeClient.admin().indices().create(cir, cirListener); + } + } + + private void createResource(CreateSampleResourceRequest request, ActionListener listener) { + SharableResource sample = request.getResource(); + try { + IndexRequest ir = nodeClient.prepareIndex(resourceIndex) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setSource(sample.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .request(); + + ActionListener irListener = ActionListener.wrap(idxResponse -> { + listener.onResponse(new CreateSampleResourceResponse(idxResponse.getId())); + }, listener::onFailure); + nodeClient.index(ir, irListener); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceAction.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceAction.java new file mode 100644 index 0000000000000..082e6c18f6c54 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceAction.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample.action.get; + +import org.opensearch.action.ActionType; +import org.opensearch.plugins.resource.action.generic.get.GetResourceResponse; +import org.opensearch.plugins.resource.sample.SampleResource; + +/** + * Action to get a sample resource + */ +public class GetSampleResourceAction extends ActionType> { + /** + * Get sample resource action instance + */ + public static final GetSampleResourceAction INSTANCE = new GetSampleResourceAction(); + /** + * Get sample resource action name + */ + public static final String NAME = "cluster:admin/sampleresource/get"; + + private GetSampleResourceAction() { + super(NAME, in -> new GetResourceResponse<>(in, SampleResource::new)); + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceRestAction.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceRestAction.java new file mode 100644 index 0000000000000..714f65453e325 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceRestAction.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample.action.get; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.plugins.resource.action.generic.get.GetResourceRequest; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.opensearch.plugins.resource.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class GetSampleResourceRestAction extends BaseRestHandler { + + public GetSampleResourceRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(GET, "/_plugins/resource_sharing_example/resource/{id}")); + } + + @Override + public String getName() { + return "get_sample_resource"; + } + + @SuppressWarnings("unchecked") + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + String resourceId = request.param("id"); + + final GetResourceRequest getSampleResourceRequest = new GetResourceRequest(resourceId, RESOURCE_INDEX_NAME); + return channel -> client.executeLocally( + GetSampleResourceAction.INSTANCE, + getSampleResourceRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceTransportAction.java b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceTransportAction.java new file mode 100644 index 0000000000000..0fee37c044b05 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/plugins/resource/sample/action/get/GetSampleResourceTransportAction.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.sample.action.get; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.plugins.resource.action.generic.get.GetResourceTransportAction; +import org.opensearch.plugins.resource.sample.SampleResource; +import org.opensearch.plugins.resource.sample.SampleResourceParser; +import org.opensearch.plugins.resource.sample.SampleResourceType; +import org.opensearch.transport.TransportService; + +import static org.opensearch.plugins.resource.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +/** + * Transport action for GetSampleResource. + */ +public class GetSampleResourceTransportAction extends GetResourceTransportAction { + private static final Logger log = LogManager.getLogger(GetSampleResourceTransportAction.class); + + @Inject + public GetSampleResourceTransportAction( + TransportService transportService, + ActionFilters actionFilters, + Client client, + NamedXContentRegistry xContentRegistry + ) { + super( + transportService, + actionFilters, + GetSampleResourceAction.NAME, + RESOURCE_INDEX_NAME, + SampleResourceType.getInstance().getResourceSharingService(), + new SampleResourceParser(), + client, + xContentRegistry + ); + } +} diff --git a/server/src/main/java/org/opensearch/action/ResourceRequest.java b/server/src/main/java/org/opensearch/action/ResourceRequest.java new file mode 100644 index 0000000000000..bc56a814d0eff --- /dev/null +++ b/server/src/main/java/org/opensearch/action/ResourceRequest.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +import java.io.IOException; + +/** + * Base class for all resource requests + */ +public class ResourceRequest extends ActionRequest { + protected final String resourceIndex; + + /** + * Default constructor + */ + public ResourceRequest(String resourceIndex) { + this.resourceIndex = resourceIndex; + } + + public ResourceRequest(StreamInput in) throws IOException { + this.resourceIndex = in.readString(); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceIndex); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceIndex() { + return this.resourceIndex; + } +} diff --git a/server/src/main/java/org/opensearch/node/Node.java b/server/src/main/java/org/opensearch/node/Node.java index c78ee6711dcda..f9a6460861b12 100644 --- a/server/src/main/java/org/opensearch/node/Node.java +++ b/server/src/main/java/org/opensearch/node/Node.java @@ -214,6 +214,8 @@ import org.opensearch.plugins.Plugin; import org.opensearch.plugins.PluginsService; import org.opensearch.plugins.RepositoryPlugin; +import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.plugins.ResourcePlugin; import org.opensearch.plugins.ScriptPlugin; import org.opensearch.plugins.SearchPipelinePlugin; import org.opensearch.plugins.SearchPlugin; @@ -222,6 +224,7 @@ import org.opensearch.plugins.TaskManagerClientPlugin; import org.opensearch.plugins.TelemetryAwarePlugin; import org.opensearch.plugins.TelemetryPlugin; +import org.opensearch.plugins.resource.ResourceService; import org.opensearch.ratelimitting.admissioncontrol.AdmissionControlService; import org.opensearch.ratelimitting.admissioncontrol.transport.AdmissionControlTransportInterceptor; import org.opensearch.repositories.RepositoriesModule; @@ -1046,6 +1049,13 @@ protected Node( List identityAwarePlugins = pluginsService.filterPlugins(IdentityAwarePlugin.class); identityService.initializeIdentityAwarePlugins(identityAwarePlugins); + final List resourceAccessControlPlugins = pluginsService.filterPlugins( + ResourceAccessControlPlugin.class + ); + final List resourcePlugins = pluginsService.filterPlugins(ResourcePlugin.class); + ResourceService resourceService = new ResourceService(resourceAccessControlPlugins); + resourceService.initializeResourcePlugins(resourcePlugins); + final QueryGroupResourceUsageTrackerService queryGroupResourceUsageTrackerService = new QueryGroupResourceUsageTrackerService( taskResourceTrackingService ); diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java new file mode 100644 index 0000000000000..edaa935417922 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins; + +import org.opensearch.plugins.resource.SharableResourceType; + +/** + * A ResourceAccessControlPlugin is responsible for assigned a resource sharing service for each + * {@link SharableResourceType} that is registered by a {@link ResourcePlugin}. There can only be a single + * ResourceAccessControlPlugin installed. + */ +public interface ResourceAccessControlPlugin { + void assignResourceSharingService(SharableResourceType resourceType); +} diff --git a/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java new file mode 100644 index 0000000000000..ae427087e5f59 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins; + +import org.opensearch.plugins.resource.SharableResourceType; + +import java.util.List; + +/** + * A ResourcePlugin registers a list of {@link SharableResourceType}. These are resources created by the plugin + * and typically stored in a system index. Resources are provided protection by the {@link ResourceAccessControlPlugin}. + */ +public interface ResourcePlugin { + List getResourceTypes(); +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/ResourceParser.java b/server/src/main/java/org/opensearch/plugins/resource/ResourceParser.java new file mode 100644 index 0000000000000..f9fc9a6ad32ac --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/ResourceParser.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource; + +import org.opensearch.core.xcontent.XContentParser; + +import java.io.IOException; + +/** + * Parser for Resource + * + * @param Returns instance of a Resource + */ +public interface ResourceParser { + T parse(XContentParser xContentParser, String id) throws IOException; +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/ResourceService.java b/server/src/main/java/org/opensearch/plugins/resource/ResourceService.java new file mode 100644 index 0000000000000..5bbf61c028c84 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/ResourceService.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.OpenSearchException; +import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.plugins.ResourcePlugin; +import org.opensearch.plugins.resource.noop.NoopResourceAccessControlPlugin; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Service to get the current ResourcePlugin to perform authorization. + * + * @opensearch.experimental + */ +public class ResourceService { + private static final Logger log = LogManager.getLogger(ResourceService.class); + + private final ResourceAccessControlPlugin controlPlugin; + + public ResourceService(final List plugins) { + + if (plugins.isEmpty()) { + log.debug("resource access control plugins size is 0. Using noop."); + controlPlugin = new NoopResourceAccessControlPlugin(); + } else if (plugins.size() == 1) { + log.debug("resource access control plugin installed."); + controlPlugin = plugins.get(0); + } else { + throw new OpenSearchException( + "Multiple resource access control plugins are not supported, found: " + + plugins.stream().map(Object::getClass).map(Class::getName).collect(Collectors.joining(",")) + ); + } + } + + public void initializeResourcePlugins(final List resourcePlugins) { + if (resourcePlugins != null) { + for (ResourcePlugin plugin : resourcePlugins) { + List pluginSharableResourceTypes = plugin.getResourceTypes(); + for (SharableResourceType resourceType : pluginSharableResourceTypes) { + controlPlugin.assignResourceSharingService(resourceType); + } + } + } + } +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/ResourceSharingService.java b/server/src/main/java/org/opensearch/plugins/resource/ResourceSharingService.java new file mode 100644 index 0000000000000..a92624f65af99 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/ResourceSharingService.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource; + +import org.opensearch.core.action.ActionListener; + +/** + * Interface for Resource Sharing Service + */ +public interface ResourceSharingService { + + /** + * Returns the resource type this service is responsible for. + * + * @return The resource type. Will match {@link SharableResourceType#getResourceType()} for the respective + * sharable resource type + */ + String getResourceType(); + + /** + * Checks if the resource is shared with the current requester. + * + * @param resourceId The resource id + * @param shareListener The listener to be called when the check is complete + */ + void isSharedWithCurrentRequester(String resourceId, ActionListener shareListener); +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/SharableResource.java b/server/src/main/java/org/opensearch/plugins/resource/SharableResource.java new file mode 100644 index 0000000000000..3fa63273cac65 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/SharableResource.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.xcontent.ToXContentObject; + +import java.time.Instant; + +/** + * Interface for a generic Sharable Resource. Resources are entities created by plugins that are typically + * stored in system indices. Access Control is provided by the ResourceAccessControlPlugin. + */ +public interface SharableResource extends NamedWriteable, ToXContentObject { + + /** + * @return resource name. + */ + String getName(); + + /** + * @return resource last update time. + */ + Instant getLastUpdateTime(); +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/SharableResourceType.java b/server/src/main/java/org/opensearch/plugins/resource/SharableResourceType.java new file mode 100644 index 0000000000000..bd024983761b0 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/SharableResourceType.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource; + +/** + * Interface for a resource type + */ +public interface SharableResourceType { + /** + * Type of the resource. This must be unique across all registered resource types. + * @return a string containing the type of the resource + */ + String getResourceType(); + + /** + * The index where resource metadata is stored + * @return the name of the index where resource metadata is stored + */ + String getResourceIndex(); + + /** + * This method is called when initializing ResourcePlugins to assign an instance + * of {@link ResourceSharingService} to the resource type. + */ + void assignResourceSharingService(ResourceSharingService resourceSharingService); + + /** + * @return returns a parser for this resource + */ + default ResourceParser getResourceParser() { + return null; + }; +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceRequest.java b/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceRequest.java new file mode 100644 index 0000000000000..b76705f9e9d56 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceRequest.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.action.generic.get; + +import org.opensearch.action.ResourceRequest; +import org.opensearch.core.common.io.stream.StreamInput; + +import java.io.IOException; + +/** + * Request object for GetResource transport action + */ +public class GetResourceRequest extends ResourceRequest { + private final String resourceId; + + /** + * Default constructor + */ + public GetResourceRequest(String resourceId, String resourceIndex) { + super(resourceIndex); + this.resourceId = resourceId; + } + + public GetResourceRequest(StreamInput in) throws IOException { + super(in); + this.resourceId = in.readString(); + } + + public String getResourceId() { + return this.resourceId; + } +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceResponse.java b/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceResponse.java new file mode 100644 index 0000000000000..31c78d76930ca --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceResponse.java @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.action.generic.get; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.plugins.resource.SharableResource; + +import java.io.IOException; + +/** + * Response to a GetResourceRequest + */ +public class GetResourceResponse extends ActionResponse implements ToXContentObject { + private final T resource; + + /** + * Default constructor + * + * @param resource The resource + */ + public GetResourceResponse(T resource) { + this.resource = resource; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + resource.writeTo(out); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public GetResourceResponse(final StreamInput in, Reader resourceReader) throws IOException { + resource = resourceReader.read(in); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("resource", resource); + builder.endObject(); + return builder; + } +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceTransportAction.java b/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceTransportAction.java new file mode 100644 index 0000000000000..45e744d761f90 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/GetResourceTransportAction.java @@ -0,0 +1,131 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.action.generic.get; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.OpenSearchException; +import org.opensearch.action.get.GetRequest; +import org.opensearch.action.get.GetResponse; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.plugins.resource.ResourceParser; +import org.opensearch.plugins.resource.ResourceSharingService; +import org.opensearch.plugins.resource.SharableResource; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import java.io.IOException; +import java.util.Objects; + +/** + * Transport action for GetResource. + */ +public class GetResourceTransportAction extends HandledTransportAction< + GetResourceRequest, + GetResourceResponse> { + private static final Logger log = LogManager.getLogger(GetResourceTransportAction.class); + + private final ResourceSharingService resourceSharingService; + + private final ResourceParser resourceParser; + + private final String resourceIndex; + + private final Client client; + + private final NamedXContentRegistry xContentRegistry; + + public GetResourceTransportAction( + TransportService transportService, + ActionFilters actionFilters, + String actionName, + String resourceIndex, + ResourceSharingService resourceSharingService, + ResourceParser resourceParser, + Client client, + NamedXContentRegistry xContentRegistry + ) { + super(actionName, transportService, actionFilters, GetResourceRequest::new); + this.resourceSharingService = resourceSharingService; + Objects.requireNonNull(resourceParser); + this.resourceParser = resourceParser; + this.resourceIndex = resourceIndex; + this.client = client; + this.xContentRegistry = xContentRegistry; + } + + @Override + protected void doExecute(Task task, GetResourceRequest request, ActionListener> actionListener) { + getResource(request, actionListener); + } + + private void getResource(GetResourceRequest request, ActionListener> listener) { + ActionListener getResourceListener = ActionListener.wrap( + resource -> { listener.onResponse(new GetResourceResponse(resource)); }, + listener::onFailure + ); + + try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashContext()) { + GetRequest gr = new GetRequest(resourceIndex); + gr.id(request.getResourceId()); + ActionListener getListener = new ActionListener<>() { + @Override + public void onResponse(GetResponse getResponse) { + try { + XContentParser parser = XContentHelper.createParser( + xContentRegistry, + LoggingDeprecationHandler.INSTANCE, + getResponse.getSourceAsBytesRef(), + XContentType.JSON + ); + T resource = resourceParser.parse(parser, getResponse.getId()); + ActionListener shareListener = new ActionListener<>() { + @Override + public void onResponse(Boolean isShared) { + if (isShared) { + getResourceListener.onResponse(resource); + } else { + getResourceListener.onFailure( + new OpenSearchException("User is not authorized to access this resource") + ); + } + } + + @Override + public void onFailure(Exception e) { + getResourceListener.onFailure( + new OpenSearchException("Failed to check sharing status: " + e.getMessage(), e) + ); + } + }; + + resourceSharingService.isSharedWithCurrentRequester(request.getResourceId(), shareListener); + } catch (IOException e) { + throw new OpenSearchException("Caught exception while loading resources: " + e.getMessage()); + } + } + + @Override + public void onFailure(Exception e) { + throw new OpenSearchException("Caught exception while loading resources: " + e.getMessage()); + } + }; + client.get(gr, getListener); + } + } +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/package-info.java b/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/package-info.java new file mode 100644 index 0000000000000..833171061020d --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/action/generic/get/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * Contains generic re-usable action for getting single plugin resource + */ +package org.opensearch.plugins.resource.action.generic.get; diff --git a/server/src/main/java/org/opensearch/plugins/resource/action/package-info.java b/server/src/main/java/org/opensearch/plugins/resource/action/package-info.java new file mode 100644 index 0000000000000..5e0824cca69d2 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/action/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * Contains generic re-usable actions for common operations related to plugin resources + */ +package org.opensearch.plugins.resource.action; diff --git a/server/src/main/java/org/opensearch/plugins/resource/noop/NoopResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/resource/noop/NoopResourceAccessControlPlugin.java new file mode 100644 index 0000000000000..b5eb0143610df --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/noop/NoopResourceAccessControlPlugin.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.noop; + +import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.plugins.resource.SharableResourceType; + +/** + * Noop implementation of Resource Access Control Plugin + */ +public class NoopResourceAccessControlPlugin implements ResourceAccessControlPlugin { + + @Override + public void assignResourceSharingService(SharableResourceType sharableResourceType) { + sharableResourceType.assignResourceSharingService(new NoopResourceSharingService(sharableResourceType.getResourceType())); + } +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/noop/NoopResourceSharingService.java b/server/src/main/java/org/opensearch/plugins/resource/noop/NoopResourceSharingService.java new file mode 100644 index 0000000000000..a933257b4ef07 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/noop/NoopResourceSharingService.java @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins.resource.noop; + +import org.opensearch.core.action.ActionListener; +import org.opensearch.plugins.resource.ResourceSharingService; + +/** + * Noop implementation of Resource Sharing Service + */ +public class NoopResourceSharingService implements ResourceSharingService { + + private final String resourceType; + + public NoopResourceSharingService(String resourceType) { + this.resourceType = resourceType; + } + + @Override + public void isSharedWithCurrentRequester(String resourceId, ActionListener shareListener) { + shareListener.onResponse(Boolean.TRUE); + } + + @Override + public String getResourceType() { + return resourceType; + } +} diff --git a/server/src/main/java/org/opensearch/plugins/resource/noop/package-info.java b/server/src/main/java/org/opensearch/plugins/resource/noop/package-info.java new file mode 100644 index 0000000000000..26d8bf80d5ce9 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/noop/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * Contains noop implementation of interfaces related to plugin resources + */ +package org.opensearch.plugins.resource.noop; diff --git a/server/src/main/java/org/opensearch/plugins/resource/package-info.java b/server/src/main/java/org/opensearch/plugins/resource/package-info.java new file mode 100644 index 0000000000000..25cad10e5021e --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/resource/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * Contains classes related to plugin resources + */ +package org.opensearch.plugins.resource;