diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/query_ruleset.put.json b/rest-api-spec/src/main/resources/rest-api-spec/api/query_ruleset.put.json new file mode 100644 index 0000000000000..7967a9b90a707 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/query_ruleset.put.json @@ -0,0 +1,45 @@ +{ + "query_ruleset.put": { + "documentation": { + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/put-query-ruleset.html", + "description": "Creates or updates a query ruleset." + }, + "stability": "experimental", + "visibility": "feature_flag", + "feature_flag": "es.query_rules_feature_flag_enabled", + "headers": { + "accept": [ + "application/json" + ], + "content_type": [ + "application/json" + ] + }, + "url": { + "paths": [ + { + "path": "/_query_rules/{ruleset_id}", + "methods": [ + "PUT" + ], + "parts": { + "ruleset_id": { + "type": "string", + "description": "The unique identifier of the ruleset to be created or updated." + } + } + } + ] + }, + "params": { + "create": { + "type": "boolean", + "description": "If true, requires that a query_ruleset with the specified resource_id does not already exist. (default: false)" + } + }, + "body": { + "description": "The query ruleset configuration, including `rules`", + "required": true + } + } +} diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/FeatureFlag.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/FeatureFlag.java index ef602257cab69..8c8fe355a60f0 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/FeatureFlag.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/FeatureFlag.java @@ -18,7 +18,8 @@ public enum FeatureFlag { TIME_SERIES_MODE("es.index_mode_feature_flag_registered=true", Version.fromString("8.0.0"), null), NEW_RCS_MODE("es.untrusted_remote_cluster_feature_flag_registered=true", Version.fromString("8.5.0"), null), DLM_ENABLED("es.dlm_feature_flag_enabled=true", Version.fromString("8.8.0"), null), - SYNONYMS_ENABLED("es.synonyms_feature_flag_enabled=true", Version.fromString("8.9.0"), null); + SYNONYMS_ENABLED("es.synonyms_feature_flag_enabled=true", Version.fromString("8.9.0"), null), + QUERY_RULES_ENABLED("es.query_rules_feature_flag_enabled=true", Version.fromString("8.9.0"), null); public final String systemProperty; public final Version from; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java index 31caf66d817fa..3f785eb6f2a04 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java @@ -107,13 +107,6 @@ public Iterator> settings() { Setting.Property.NodeScope ); - /** Setting for enabling or disabling query rules. Defaults to false. */ - public static final Setting ENTERPRISE_SEARCH_QUERY_RULES_ENABLED = Setting.boolSetting( - "xpack.ent_search.query_rules.enabled", - false, - Setting.Property.NodeScope - ); - /** Setting for enabling or disabling auditing. Defaults to false. */ public static final Setting AUDIT_ENABLED = Setting.boolSetting( "xpack.security.audit.enabled", diff --git a/x-pack/plugin/ent-search/qa/rest/build.gradle b/x-pack/plugin/ent-search/qa/rest/build.gradle index 3b809445e7b79..2be2ac72cf11e 100644 --- a/x-pack/plugin/ent-search/qa/rest/build.gradle +++ b/x-pack/plugin/ent-search/qa/rest/build.gradle @@ -7,7 +7,7 @@ dependencies { restResources { restApi { - include '_common', 'cluster', 'nodes', 'indices', 'index', 'search_application', 'xpack' + include '_common', 'cluster', 'nodes', 'indices', 'index', 'query_ruleset', 'search_application', 'xpack' } } diff --git a/x-pack/plugin/ent-search/qa/rest/roles.yml b/x-pack/plugin/ent-search/qa/rest/roles.yml index f46ec4ef96d40..4ab49432c70b0 100644 --- a/x-pack/plugin/ent-search/qa/rest/roles.yml +++ b/x-pack/plugin/ent-search/qa/rest/roles.yml @@ -2,6 +2,7 @@ admin: cluster: - manage_search_application - manage_behavioral_analytics + - manage - monitor indices: - names: [ diff --git a/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/200_query_ruleset_put.yml b/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/200_query_ruleset_put.yml new file mode 100644 index 0000000000000..0225735128b61 --- /dev/null +++ b/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/200_query_ruleset_put.yml @@ -0,0 +1,102 @@ + + +--- +'Create Query Ruleset': + - do: + query_ruleset.put: + ruleset_id: test-ruleset + body: + ruleset_id: test-ruleset + rules: + - rule_id: query-rule-id1 + type: pinned + criteria: + - type: exact + metadata: query_string + value: elastic + actions: + ids: + - 'id1' + - 'id2' + - rule_id: query-rule-id2 + type: pinned + criteria: + - type: exact + metadata: query_string + value: kibana + actions: + docs: + - '_index': 'test-index1' + '_id': 'id3' + - '_index': 'test-index2' + '_id': 'id4' + + - match: { result: 'created' } + +--- +'Create Query Ruleset - Resource already exists': + - do: + query_ruleset.put: + create: true + ruleset_id: test-query-ruleset-recreating + body: + ruleset_id: 'test-query-ruleset-recreating' + rules: + rule_id: 'test-rule-1' + type: 'pinned' + criteria: + type: 'exact' + metadata: 'query_string' + value: 'elastic' + actions: + ids: + - 'id1' + + - match: { result: 'created' } + + - do: + catch: conflict + query_ruleset.put: + create: true + ruleset_id: test-query-ruleset-recreating + body: + ruleset_id: 'test-query-ruleset-recreating' + rules: + rule_id: 'test-rule-1' + type: 'pinned' + criteria: + type: 'exact' + metadata: 'query_string' + value: 'elastic' + actions: + ids: + - 'id2' + + - match: { error.type: 'version_conflict_engine_exception' } + +--- +'Create Query Ruleset - Insufficient privilege': + - skip: + features: headers + + - do: + catch: forbidden + headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user + query_ruleset.put: + ruleset_id: forbidden-query-ruleset + create: true + body: + ruleset_id: 'forbidden-query-ruleset' + rules: + rule_id: 'test-rule-1' + type: 'pinned' + criteria: + type: 'exact' + metadata: 'query_string' + value: 'elastic' + actions: + ids: + - 'id1' + - 'id2' + + - match: { error.type: 'security_exception' } diff --git a/x-pack/plugin/ent-search/src/main/java/module-info.java b/x-pack/plugin/ent-search/src/main/java/module-info.java index b58c76687cac4..077f511ec753a 100644 --- a/x-pack/plugin/ent-search/src/main/java/module-info.java +++ b/x-pack/plugin/ent-search/src/main/java/module-info.java @@ -30,4 +30,5 @@ exports org.elasticsearch.xpack.application.search; exports org.elasticsearch.xpack.application.search.action; + exports org.elasticsearch.xpack.application.rules.action; } diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java index d260f2095631a..63f98f8aec61c 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java @@ -20,6 +20,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; +import org.elasticsearch.common.util.FeatureFlag; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.indices.SystemIndexDescriptor; @@ -52,6 +53,9 @@ import org.elasticsearch.xpack.application.analytics.action.TransportPutAnalyticsCollectionAction; import org.elasticsearch.xpack.application.analytics.ingest.AnalyticsEventIngestConfig; import org.elasticsearch.xpack.application.rules.QueryRulesIndexService; +import org.elasticsearch.xpack.application.rules.action.PutQueryRulesetAction; +import org.elasticsearch.xpack.application.rules.action.RestPutQueryRulesetAction; +import org.elasticsearch.xpack.application.rules.action.TransportPutQueryRulesetAction; import org.elasticsearch.xpack.application.search.SearchApplicationIndexService; import org.elasticsearch.xpack.application.search.action.DeleteSearchApplicationAction; import org.elasticsearch.xpack.application.search.action.GetSearchApplicationAction; @@ -76,6 +80,7 @@ import org.elasticsearch.xpack.core.action.XPackInfoFeatureAction; import org.elasticsearch.xpack.core.action.XPackUsageFeatureAction; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -90,17 +95,18 @@ public class EnterpriseSearch extends Plugin implements ActionPlugin, SystemInde public static final String BEHAVIORAL_ANALYTICS_API_ENDPOINT = APPLICATION_API_ENDPOINT + "/analytics"; + public static final String QUERY_RULES_API_ENDPOINT = "_query_rules"; + private static final Logger logger = LogManager.getLogger(EnterpriseSearch.class); public static final String FEATURE_NAME = "ent_search"; private final boolean enabled; - private final boolean queryRulesEnabled; + private static final FeatureFlag QUERY_RULES_FEATURE_FLAG = new FeatureFlag("query_rules"); public EnterpriseSearch(Settings settings) { this.enabled = XPackSettings.ENTERPRISE_SEARCH_ENABLED.get(settings); - this.queryRulesEnabled = XPackSettings.ENTERPRISE_SEARCH_QUERY_RULES_ENABLED.get(settings); } protected XPackLicenseState getLicenseState() { @@ -114,20 +120,29 @@ protected XPackLicenseState getLicenseState() { if (enabled == false) { return List.of(usageAction, infoAction); } - return List.of( - new ActionHandler<>(PutAnalyticsCollectionAction.INSTANCE, TransportPutAnalyticsCollectionAction.class), - new ActionHandler<>(GetAnalyticsCollectionAction.INSTANCE, TransportGetAnalyticsCollectionAction.class), - new ActionHandler<>(DeleteAnalyticsCollectionAction.INSTANCE, TransportDeleteAnalyticsCollectionAction.class), - new ActionHandler<>(PostAnalyticsEventAction.INSTANCE, TransportPostAnalyticsEventAction.class), - new ActionHandler<>(DeleteSearchApplicationAction.INSTANCE, TransportDeleteSearchApplicationAction.class), - new ActionHandler<>(GetSearchApplicationAction.INSTANCE, TransportGetSearchApplicationAction.class), - new ActionHandler<>(ListSearchApplicationAction.INSTANCE, TransportListSearchApplicationAction.class), - new ActionHandler<>(PutSearchApplicationAction.INSTANCE, TransportPutSearchApplicationAction.class), - new ActionHandler<>(QuerySearchApplicationAction.INSTANCE, TransportQuerySearchApplicationAction.class), - new ActionHandler<>(RenderSearchApplicationQueryAction.INSTANCE, TransportRenderSearchApplicationQueryAction.class), - usageAction, - infoAction + + final List> actionHandlers = new ArrayList<>( + List.of( + new ActionHandler<>(PutAnalyticsCollectionAction.INSTANCE, TransportPutAnalyticsCollectionAction.class), + new ActionHandler<>(GetAnalyticsCollectionAction.INSTANCE, TransportGetAnalyticsCollectionAction.class), + new ActionHandler<>(DeleteAnalyticsCollectionAction.INSTANCE, TransportDeleteAnalyticsCollectionAction.class), + new ActionHandler<>(PostAnalyticsEventAction.INSTANCE, TransportPostAnalyticsEventAction.class), + new ActionHandler<>(DeleteSearchApplicationAction.INSTANCE, TransportDeleteSearchApplicationAction.class), + new ActionHandler<>(GetSearchApplicationAction.INSTANCE, TransportGetSearchApplicationAction.class), + new ActionHandler<>(ListSearchApplicationAction.INSTANCE, TransportListSearchApplicationAction.class), + new ActionHandler<>(PutSearchApplicationAction.INSTANCE, TransportPutSearchApplicationAction.class), + new ActionHandler<>(QuerySearchApplicationAction.INSTANCE, TransportQuerySearchApplicationAction.class), + new ActionHandler<>(RenderSearchApplicationQueryAction.INSTANCE, TransportRenderSearchApplicationQueryAction.class), + usageAction, + infoAction + ) ); + + if (QUERY_RULES_FEATURE_FLAG.isEnabled()) { + actionHandlers.add(new ActionHandler<>(PutQueryRulesetAction.INSTANCE, TransportPutQueryRulesetAction.class)); + } + + return Collections.unmodifiableList(actionHandlers); } @Override @@ -144,18 +159,27 @@ public List getRestHandlers( if (enabled == false) { return Collections.emptyList(); } - return List.of( - new RestGetSearchApplicationAction(getLicenseState()), - new RestListSearchApplicationAction(getLicenseState()), - new RestPutSearchApplicationAction(getLicenseState()), - new RestDeleteSearchApplicationAction(getLicenseState()), - new RestQuerySearchApplicationAction(getLicenseState()), - new RestPutAnalyticsCollectionAction(getLicenseState()), - new RestGetAnalyticsCollectionAction(getLicenseState()), - new RestDeleteAnalyticsCollectionAction(getLicenseState()), - new RestPostAnalyticsEventAction(getLicenseState()), - new RestRenderSearchApplicationQueryAction(getLicenseState()) + + final List restHandlers = new ArrayList<>( + List.of( + new RestPutAnalyticsCollectionAction(getLicenseState()), + new RestGetAnalyticsCollectionAction(getLicenseState()), + new RestDeleteAnalyticsCollectionAction(getLicenseState()), + new RestPostAnalyticsEventAction(getLicenseState()), + new RestDeleteSearchApplicationAction(getLicenseState()), + new RestGetSearchApplicationAction(getLicenseState()), + new RestListSearchApplicationAction(getLicenseState()), + new RestPutSearchApplicationAction(getLicenseState()), + new RestQuerySearchApplicationAction(getLicenseState()), + new RestRenderSearchApplicationQueryAction(getLicenseState()) + ) ); + + if (QUERY_RULES_FEATURE_FLAG.isEnabled()) { + restHandlers.add(new RestPutQueryRulesetAction(getLicenseState())); + } + + return Collections.unmodifiableList(restHandlers); } @Override @@ -192,7 +216,7 @@ public Collection createComponents( @Override public Collection getSystemIndexDescriptors(Settings settings) { - if (queryRulesEnabled) { + if (QUERY_RULES_FEATURE_FLAG.isEnabled()) { return Arrays.asList( SearchApplicationIndexService.getSystemIndexDescriptor(), QueryRulesIndexService.getSystemIndexDescriptor() diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetAction.java new file mode 100644 index 0000000000000..eac7a5705bb60 --- /dev/null +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetAction.java @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.application.rules.action; + +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.StatusToXContentObject; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.application.rules.QueryRule; +import org.elasticsearch.xpack.application.rules.QueryRuleset; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +import static org.elasticsearch.action.ValidateActions.addValidationError; + +public class PutQueryRulesetAction extends ActionType { + + public static final PutQueryRulesetAction INSTANCE = new PutQueryRulesetAction(); + public static final String NAME = "cluster:admin/xpack/query_rules/put"; + + public PutQueryRulesetAction() { + super(NAME, PutQueryRulesetAction.Response::new); + } + + public static class Request extends ActionRequest { + + private final QueryRuleset queryRuleset; + private final boolean create; + + public Request(StreamInput in) throws IOException { + super(in); + this.queryRuleset = new QueryRuleset(in); + this.create = in.readBoolean(); + } + + public Request(QueryRuleset queryRuleset, boolean create) { + this.queryRuleset = queryRuleset; + this.create = create; + } + + public Request(String rulesetId, boolean create, BytesReference content, XContentType contentType) { + this.queryRuleset = QueryRuleset.fromXContentBytes(rulesetId, content, contentType); + this.create = create; + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + + if (Strings.isNullOrEmpty(queryRuleset.id())) { + validationException = addValidationError("ruleset_id cannot be null or empty", validationException); + } + + List rules = queryRuleset.rules(); + if (rules == null || rules.isEmpty()) { + validationException = addValidationError("rules cannot be null or empty", validationException); + } + + return validationException; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + queryRuleset.writeTo(out); + out.writeBoolean(create); + } + + public QueryRuleset queryRuleset() { + return queryRuleset; + } + + public boolean create() { + return create; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return create == request.create && Objects.equals(queryRuleset, request.queryRuleset); + } + + @Override + public int hashCode() { + return Objects.hash(queryRuleset, create); + } + } + + public static class Response extends ActionResponse implements StatusToXContentObject { + + final DocWriteResponse.Result result; + + public Response(StreamInput in) throws IOException { + super(in); + result = DocWriteResponse.Result.readFrom(in); + } + + public Response(DocWriteResponse.Result result) { + this.result = result; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + this.result.writeTo(out); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("result", this.result.getLowercase()); + builder.endObject(); + return builder; + } + + @Override + public RestStatus status() { + return switch (result) { + case CREATED -> RestStatus.CREATED; + case NOT_FOUND -> RestStatus.NOT_FOUND; + default -> RestStatus.OK; + }; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Response that = (Response) o; + return Objects.equals(result, that.result); + } + + @Override + public int hashCode() { + return Objects.hash(result); + } + + } + +} diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/RestPutQueryRulesetAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/RestPutQueryRulesetAction.java new file mode 100644 index 0000000000000..fcb22fcd22929 --- /dev/null +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/RestPutQueryRulesetAction.java @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.application.rules.action; + +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.rest.Scope; +import org.elasticsearch.rest.ServerlessScope; +import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.xpack.application.EnterpriseSearch; +import org.elasticsearch.xpack.application.EnterpriseSearchBaseRestHandler; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.PUT; + +@ServerlessScope(Scope.PUBLIC) +public class RestPutQueryRulesetAction extends EnterpriseSearchBaseRestHandler { + public RestPutQueryRulesetAction(XPackLicenseState licenseState) { + super(licenseState); + } + + @Override + public String getName() { + return "query_ruleset_put_action"; + } + + @Override + public List routes() { + return List.of(new Route(PUT, "/" + EnterpriseSearch.QUERY_RULES_API_ENDPOINT + "/{ruleset_id}")); + } + + @Override + protected RestChannelConsumer innerPrepareRequest(RestRequest restRequest, NodeClient client) throws IOException { + PutQueryRulesetAction.Request request = new PutQueryRulesetAction.Request( + restRequest.param("ruleset_id"), + restRequest.paramAsBoolean("create", false), + restRequest.content(), + restRequest.getXContentType() + ); + return channel -> client.execute(PutQueryRulesetAction.INSTANCE, request, new RestToXContentListener<>(channel) { + @Override + protected RestStatus getStatus(PutQueryRulesetAction.Response response) { + return response.status(); + } + }); + } +} diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/TransportPutQueryRulesetAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/TransportPutQueryRulesetAction.java new file mode 100644 index 0000000000000..0bfaf544b0386 --- /dev/null +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/TransportPutQueryRulesetAction.java @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.application.rules.action; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.HandledTransportAction; +import org.elasticsearch.client.internal.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.application.rules.QueryRulesIndexService; +import org.elasticsearch.xpack.application.rules.QueryRuleset; + +public class TransportPutQueryRulesetAction extends HandledTransportAction { + protected final QueryRulesIndexService systemIndexService; + + @Inject + public TransportPutQueryRulesetAction(TransportService transportService, ActionFilters actionFilters, Client client) { + super(PutQueryRulesetAction.NAME, transportService, actionFilters, PutQueryRulesetAction.Request::new); + this.systemIndexService = new QueryRulesIndexService(client); + } + + @Override + protected void doExecute(Task task, PutQueryRulesetAction.Request request, ActionListener listener) { + QueryRuleset queryRuleset = request.queryRuleset(); + boolean create = request.create(); + systemIndexService.putQueryRuleset(queryRuleset, create, listener.map(r -> new PutQueryRulesetAction.Response(r.getResult()))); + + } +} diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetActionRequestSerializingTests.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetActionRequestSerializingTests.java new file mode 100644 index 0000000000000..92f72a800d2bd --- /dev/null +++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetActionRequestSerializingTests.java @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.application.rules.action; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xpack.application.search.SearchApplicationTestUtils; + +public class PutQueryRulesetActionRequestSerializingTests extends AbstractWireSerializingTestCase { + + @Override + protected Writeable.Reader instanceReader() { + return PutQueryRulesetAction.Request::new; + } + + @Override + protected PutQueryRulesetAction.Request createTestInstance() { + return new PutQueryRulesetAction.Request(SearchApplicationTestUtils.randomQueryRuleset(), randomBoolean()); + } + + @Override + protected PutQueryRulesetAction.Request mutateInstance(PutQueryRulesetAction.Request instance) { + return randomValueOtherThan(instance, this::createTestInstance); + } +} diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetActionResponseSerializingTests.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetActionResponseSerializingTests.java new file mode 100644 index 0000000000000..b35380a70b636 --- /dev/null +++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/PutQueryRulesetActionResponseSerializingTests.java @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.application.rules.action; + +import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.AbstractWireSerializingTestCase; + +import java.io.IOException; + +public class PutQueryRulesetActionResponseSerializingTests extends AbstractWireSerializingTestCase { + + @Override + protected Writeable.Reader instanceReader() { + return PutQueryRulesetAction.Response::new; + } + + @Override + protected PutQueryRulesetAction.Response createTestInstance() { + return new PutQueryRulesetAction.Response(randomFrom(DocWriteResponse.Result.values())); + } + + @Override + protected PutQueryRulesetAction.Response mutateInstance(PutQueryRulesetAction.Response instance) throws IOException { + return randomValueOtherThan(instance, this::createTestInstance); + } +} diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/RestPutQueryRulesetActionTests.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/RestPutQueryRulesetActionTests.java new file mode 100644 index 0000000000000..5bb006cc64513 --- /dev/null +++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/RestPutQueryRulesetActionTests.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.application.rules.action; + +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.elasticsearch.xcontent.NamedXContentRegistry; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.application.AbstractRestEnterpriseSearchActionTests; +import org.elasticsearch.xpack.application.EnterpriseSearchBaseRestHandler; + +import java.util.Map; + +public class RestPutQueryRulesetActionTests extends AbstractRestEnterpriseSearchActionTests { + public void testWithNonCompliantLicense() throws Exception { + checkLicenseForRequest( + new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY).withMethod(RestRequest.Method.PUT) + .withParams(Map.of("ruleset_id", "ruleset-id")) + .withContent(new BytesArray(""" + { + "ruleset_id": "ruleset-id", + "rules": [ + { + "rule_id": "query-rule-id", + "type": "pinned", + "criteria": [ + { + "type": "exact", + "metadata": "query_string", + "value": "elastic" + } + ], + "actions": + { + "ids": [ + "id1", + "id2" + ] + } + } + ] + } + """), XContentType.JSON) + .build() + ); + } + + @Override + protected EnterpriseSearchBaseRestHandler getRestAction(XPackLicenseState licenseState) { + return new RestPutQueryRulesetAction(licenseState); + } +} diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java index 042c884265d87..cb2c38bd2c224 100644 --- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java +++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java @@ -190,6 +190,7 @@ public class Constants { "cluster:admin/xpack/ml/upgrade_mode", "cluster:admin/xpack/monitoring/bulk", "cluster:admin/xpack/monitoring/migrate/alerts", + "cluster:admin/xpack/query_rules/put", "cluster:admin/xpack/rollup/delete", "cluster:admin/xpack/rollup/put", "cluster:admin/xpack/rollup/start",