Bladeren bron

[Connector API] Support updating configuration values only (#105249)

Jedr Blaszyk 1 jaar geleden
bovenliggende
commit
68116164a6

+ 5 - 0
docs/changelog/105249.yaml

@@ -0,0 +1,5 @@
+pr: 105249
+summary: "[Connector API] Support updating configuration values only"
+area: Application
+type: enhancement
+issues: []

+ 135 - 4
x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/335_connector_update_configuration.yml

@@ -13,8 +13,6 @@ setup:
           is_native: false
           service_type: super-connector
 
----
-"Update Connector Configuration":
   - do:
       connector.update_configuration:
         connector_id: test-connector
@@ -38,9 +36,27 @@ setup:
                 - constraint: 0
                   type: greater_than
               value: 123
+            yet_another_field:
+              default_value: null
+              depends_on:
+                - field: some_field
+                  value: 31
+              display: numeric
+              label: Another important field
+              options: [ ]
+              order: 4
+              required: true
+              sensitive: false
+              tooltip: Wow, this tooltip is useful.
+              type: str
+              ui_restrictions: [ ]
+              validations:
+                - constraint: 0
+                  type: greater_than
+              value: "peace & love"
 
-
-  - match: { result: updated }
+---
+"Update Connector Configuration":
 
   - do:
       connector.get:
@@ -50,6 +66,7 @@ setup:
   - match: { configuration.some_field.sensitive: false }
   - match: { configuration.some_field.display: numeric }
   - match: { status: configured }
+  - match: { configuration.yet_another_field.value: "peace & love" }
 
 
   - do:
@@ -262,3 +279,117 @@ setup:
         connector_id: test-connector
 
   - match: { configuration.nextSyncConfig.value.max_crawl_depth: 3 }
+
+
+---
+"Update Connector Configuration - Clear configuration":
+  - do:
+      connector.update_configuration:
+        connector_id: test-connector
+        body:
+          configuration: {}
+
+  - match: { result: updated }
+
+  - do:
+      connector.get:
+        connector_id: test-connector
+
+  - match: { configuration: {} }
+
+
+---
+"Update Connector Configuration - Values Only":
+  - do:
+      connector.update_configuration:
+        connector_id: test-connector
+        body:
+          values:
+            some_field: 42
+            yet_another_field: 456
+
+  - match: { result: updated }
+
+  - do:
+      connector.get:
+        connector_id: test-connector
+
+  - match: { configuration.some_field.value: 42 }
+  - match: { configuration.some_field.label: Very important field }
+  - match: { configuration.yet_another_field.value: 456 }
+  - match: { configuration.yet_another_field.label: Another important field }
+
+
+---
+"Update Connector Configuration - Partial Values Update":
+  - do:
+      connector.update_configuration:
+        connector_id: test-connector
+        body:
+          values:
+            yet_another_field: 42
+
+  - match: { result: updated }
+
+  - do:
+      connector.get:
+        connector_id: test-connector
+
+  - match: { configuration.some_field.value: 123 }
+  - match: { configuration.yet_another_field.value: 42 }
+
+
+---
+"Update Connector Configuration - Update unknown field value":
+  - do:
+      catch: "bad_request"
+      connector.update_configuration:
+        connector_id: test-connector
+        body:
+          values:
+            field_not_present_in_config: 42
+
+  - match: { error.reason: "Unknown [configuration] fields in the request payload: [field_not_present_in_config]. Remove them from request or register their schema first." }
+
+
+---
+"Update Connector Configuration - 'configuration' and 'values' are both non-null":
+  - do:
+      catch: "bad_request"
+      connector.update_configuration:
+        connector_id: test-connector
+        body:
+          configuration:
+            yet_another_field:
+              default_value: null
+              depends_on:
+                - field: some_field
+                  value: 31
+              display: numeric
+              label: Another important field
+              options: [ ]
+              order: 4
+              required: true
+              sensitive: false
+              tooltip: Wow, this tooltip is useful.
+              type: str
+              ui_restrictions: [ ]
+              validations:
+                - constraint: 0
+                  type: greater_than
+              value: 42
+          values:
+            yet_another_field: 42
+
+  - match: { error.reason: "Validation Failed: 1: [configuration] and [values] cannot both be provided in the same request.;" }
+
+
+---
+"Update Connector Configuration - 'configuration' and 'values' are null":
+  - do:
+      catch: "bad_request"
+      connector.update_configuration:
+        connector_id: test-connector
+        body: {}
+
+  - match: { error.reason: "Validation Failed: 1: [configuration] and [values] cannot both be null.;" }

+ 35 - 15
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorConfiguration.java

@@ -150,7 +150,7 @@ public class ConnectorConfiguration implements Writeable, ToXContentObject {
 
     @SuppressWarnings("unchecked")
     private static final ConstructingObjectParser<ConnectorConfiguration, Void> PARSER = new ConstructingObjectParser<>(
-        "connector_configuration_dependency",
+        "connector_configuration",
         true,
         args -> {
             int i = 0;
@@ -209,20 +209,40 @@ public class ConnectorConfiguration implements Writeable, ToXContentObject {
         );
         PARSER.declareStringArray(optionalConstructorArg(), UI_RESTRICTIONS_FIELD);
         PARSER.declareObjectArray(optionalConstructorArg(), (p, c) -> ConfigurationValidation.fromXContent(p), VALIDATIONS_FIELD);
-        PARSER.declareField(optionalConstructorArg(), (p, c) -> {
-            if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                return p.text();
-            } else if (p.currentToken() == XContentParser.Token.VALUE_NUMBER) {
-                return p.numberValue();
-            } else if (p.currentToken() == XContentParser.Token.VALUE_BOOLEAN) {
-                return p.booleanValue();
-            } else if (p.currentToken() == XContentParser.Token.START_OBJECT) {
-                return p.map();
-            } else if (p.currentToken() == XContentParser.Token.VALUE_NULL) {
-                return null;
-            }
-            throw new XContentParseException("Unsupported token [" + p.currentToken() + "]");
-        }, VALUE_FIELD, ObjectParser.ValueType.VALUE_OBJECT_ARRAY);
+        PARSER.declareField(
+            optionalConstructorArg(),
+            (p, c) -> parseConfigurationValue(p),
+            VALUE_FIELD,
+            ObjectParser.ValueType.VALUE_OBJECT_ARRAY
+        );
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    /**
+     * Parses a configuration value from a parser context, supporting the {@link Connector} protocol's value types.
+     * This method can parse strings, numbers, booleans, objects, and null values, matching the types commonly
+     * supported in {@link ConnectorConfiguration}.
+     *
+     * @param p the {@link org.elasticsearch.xcontent.XContentParser} instance from which to parse the configuration value.
+     */
+    public static Object parseConfigurationValue(XContentParser p) throws IOException {
+
+        if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
+            return p.text();
+        } else if (p.currentToken() == XContentParser.Token.VALUE_NUMBER) {
+            return p.numberValue();
+        } else if (p.currentToken() == XContentParser.Token.VALUE_BOOLEAN) {
+            return p.booleanValue();
+        } else if (p.currentToken() == XContentParser.Token.START_OBJECT) {
+            // Crawler expects the value to be an object
+            return p.map();
+        } else if (p.currentToken() == XContentParser.Token.VALUE_NULL) {
+            return null;
+        }
+        throw new XContentParseException("Unsupported token [" + p.currentToken() + "]");
     }
 
     @Override

+ 103 - 33
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java

@@ -63,7 +63,9 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
 
 import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
 import static org.elasticsearch.xpack.core.ClientHelper.CONNECTORS_ORIGIN;
@@ -382,51 +384,114 @@ public class ConnectorIndexService {
 
     /**
      * Updates the {@link ConnectorConfiguration} property of a {@link Connector}.
-     * The update process is non-additive; it completely replaces all existing configuration fields with the new configuration mapping,
-     * thereby deleting any old configurations.
+     * This method supports full configuration replacement or individual configuration value updates.
+     * If a full configuration is provided, it overwrites all existing configurations in non-additive way.
+     * If only configuration values are provided, the existing {@link ConnectorConfiguration} is updated with new values
+     * provided in the request.
      *
      * @param request   Request for updating connector configuration property.
      * @param listener  Listener to respond to a successful response or an error.
      */
     public void updateConnectorConfiguration(UpdateConnectorConfigurationAction.Request request, ActionListener<UpdateResponse> listener) {
         try {
+            Map<String, ConnectorConfiguration> fullConfiguration = request.getConfiguration();
+            Map<String, Object> configurationValues = request.getConfigurationValues();
             String connectorId = request.getConnectorId();
 
-            String updateConfigurationScript = String.format(
-                Locale.ROOT,
-                """
-                    ctx._source.%s = params.%s;
-                    ctx._source.%s = params.%s;
-                    """,
-                Connector.CONFIGURATION_FIELD.getPreferredName(),
-                Connector.CONFIGURATION_FIELD.getPreferredName(),
-                Connector.STATUS_FIELD.getPreferredName(),
-                Connector.STATUS_FIELD.getPreferredName()
-            );
-            Script script = new Script(
-                ScriptType.INLINE,
-                "painless",
-                updateConfigurationScript,
-                Map.of(
-                    Connector.CONFIGURATION_FIELD.getPreferredName(),
-                    request.getConfiguration(),
-                    Connector.STATUS_FIELD.getPreferredName(),
-                    ConnectorStatus.CONFIGURED.toString()
-                )
-            );
-            final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).script(script)
-                .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
+            getConnector(connectorId, listener.delegateFailure((l, connector) -> {
 
-            clientWithOrigin.update(
-                updateRequest,
-                new DelegatingIndexNotFoundActionListener<>(connectorId, listener, (l, updateResponse) -> {
+                UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).setRefreshPolicy(
+                    WriteRequest.RefreshPolicy.IMMEDIATE
+                );
+
+                // Completely override [configuration] field with script
+                if (fullConfiguration != null) {
+                    String updateConfigurationScript = String.format(
+                        Locale.ROOT,
+                        """
+                            ctx._source.%s = params.%s;
+                            ctx._source.%s = params.%s;
+                            """,
+                        Connector.CONFIGURATION_FIELD.getPreferredName(),
+                        Connector.CONFIGURATION_FIELD.getPreferredName(),
+                        Connector.STATUS_FIELD.getPreferredName(),
+                        Connector.STATUS_FIELD.getPreferredName()
+                    );
+                    Script script = new Script(
+                        ScriptType.INLINE,
+                        "painless",
+                        updateConfigurationScript,
+                        Map.of(
+                            Connector.CONFIGURATION_FIELD.getPreferredName(),
+                            request.getConfiguration(),
+                            Connector.STATUS_FIELD.getPreferredName(),
+                            ConnectorStatus.CONFIGURED.toString()
+                        )
+                    );
+                    updateRequest = updateRequest.script(script);
+
+                }
+                // Only update configuration values for (key, value) pairs provided
+                else if (configurationValues != null) {
+
+                    Set<String> existingKeys = getConnectorConfigurationFromSearchResult(connector).keySet();
+                    Set<String> newConfigurationKeys = configurationValues.keySet();
+
+                    // Fail request it could result in updating values for unknown configuration keys
+                    if (existingKeys.containsAll(newConfigurationKeys) == false) {
+
+                        Set<String> unknownConfigKeys = newConfigurationKeys.stream()
+                            .filter(key -> existingKeys.contains(key) == false)
+                            .collect(Collectors.toSet());
+
+                        l.onFailure(
+                            new ElasticsearchStatusException(
+                                "Unknown [configuration] fields in the request payload: ["
+                                    + String.join(", ", unknownConfigKeys)
+                                    + "]. Remove them from request or register their schema first.",
+                                RestStatus.BAD_REQUEST
+                            )
+                        );
+                        return;
+                    }
+
+                    Map<String, Object> configurationValuesUpdatePayload = configurationValues.entrySet()
+                        .stream()
+                        .collect(
+                            Collectors.toMap(
+                                Map.Entry::getKey,
+                                entry -> Map.of(ConnectorConfiguration.VALUE_FIELD.getPreferredName(), entry.getValue())
+                            )
+                        );
+
+                    updateRequest = updateRequest.doc(
+                        new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
+                            .id(connectorId)
+                            .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
+                            .source(
+                                Map.of(
+                                    Connector.CONFIGURATION_FIELD.getPreferredName(),
+                                    configurationValuesUpdatePayload,
+                                    Connector.STATUS_FIELD.getPreferredName(),
+                                    ConnectorStatus.CONFIGURED.toString()
+                                )
+                            )
+                    );
+                } else {
+                    l.onFailure(
+                        new ElasticsearchStatusException("[configuration] and [values] cannot both be null.", RestStatus.BAD_REQUEST)
+                    );
+                    return;
+                }
+
+                clientWithOrigin.update(updateRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, l, (ll, updateResponse) -> {
                     if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) {
-                        l.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
+                        ll.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
                         return;
                     }
-                    l.onResponse(updateResponse);
-                })
-            );
+                    ll.onResponse(updateResponse);
+                }));
+            }));
         } catch (Exception e) {
             listener.onFailure(e);
         }
@@ -851,6 +916,11 @@ public class ConnectorIndexService {
         return ConnectorStatus.connectorStatus((String) searchResult.getResultMap().get(Connector.STATUS_FIELD.getPreferredName()));
     }
 
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> getConnectorConfigurationFromSearchResult(ConnectorSearchResult searchResult) {
+        return (Map<String, Object>) searchResult.getResultMap().get(Connector.CONFIGURATION_FIELD.getPreferredName());
+    }
+
     private static ConnectorIndexService.ConnectorResult mapSearchResponseToConnectorList(SearchResponse response) {
         final List<ConnectorSearchResult> connectorResults = Arrays.stream(response.getHits().getHits())
             .map(ConnectorIndexService::hitToConnector)

+ 29 - 6
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorConfigurationAction.java

@@ -18,6 +18,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.xcontent.ConstructingObjectParser;
 import org.elasticsearch.xcontent.ObjectParser;
+import org.elasticsearch.xcontent.ParseField;
 import org.elasticsearch.xcontent.ToXContentObject;
 import org.elasticsearch.xcontent.XContentBuilder;
 import org.elasticsearch.xcontent.XContentParser;
@@ -45,16 +46,21 @@ public class UpdateConnectorConfigurationAction {
 
         private final String connectorId;
         private final Map<String, ConnectorConfiguration> configuration;
+        private final Map<String, Object> configurationValues;
 
-        public Request(String connectorId, Map<String, ConnectorConfiguration> configuration) {
+        private static final ParseField VALUES_FIELD = new ParseField("values");
+
+        public Request(String connectorId, Map<String, ConnectorConfiguration> configuration, Map<String, Object> configurationValues) {
             this.connectorId = connectorId;
             this.configuration = configuration;
+            this.configurationValues = configurationValues;
         }
 
         public Request(StreamInput in) throws IOException {
             super(in);
             this.connectorId = in.readString();
             this.configuration = in.readMap(ConnectorConfiguration::new);
+            this.configurationValues = in.readGenericMap();
         }
 
         public String getConnectorId() {
@@ -65,6 +71,10 @@ public class UpdateConnectorConfigurationAction {
             return configuration;
         }
 
+        public Map<String, Object> getConfigurationValues() {
+            return configurationValues;
+        }
+
         @Override
         public ActionRequestValidationException validate() {
             ActionRequestValidationException validationException = null;
@@ -73,8 +83,15 @@ public class UpdateConnectorConfigurationAction {
                 validationException = addValidationError("[connector_id] cannot be [null] or [\"\"].", validationException);
             }
 
-            if (Objects.isNull(configuration)) {
-                validationException = addValidationError("[configuration] cannot be [null].", validationException);
+            if (configuration == null && configurationValues == null) {
+                validationException = addValidationError("[configuration] and [values] cannot both be null.", validationException);
+            }
+
+            if (configuration != null && configurationValues != null) {
+                validationException = addValidationError(
+                    "[configuration] and [values] cannot both be provided in the same request.",
+                    validationException
+                );
             }
 
             return validationException;
@@ -87,7 +104,8 @@ public class UpdateConnectorConfigurationAction {
                 false,
                 ((args, connectorId) -> new UpdateConnectorConfigurationAction.Request(
                     connectorId,
-                    (Map<String, ConnectorConfiguration>) args[0]
+                    (Map<String, ConnectorConfiguration>) args[0],
+                    (Map<String, Object>) args[1]
                 ))
             );
 
@@ -98,6 +116,7 @@ public class UpdateConnectorConfigurationAction {
                 Connector.CONFIGURATION_FIELD,
                 ObjectParser.ValueType.OBJECT
             );
+            PARSER.declareField(optionalConstructorArg(), (p, c) -> p.map(), VALUES_FIELD, ObjectParser.ValueType.VALUE_OBJECT_ARRAY);
         }
 
         public static UpdateConnectorConfigurationAction.Request fromXContentBytes(
@@ -122,6 +141,7 @@ public class UpdateConnectorConfigurationAction {
             builder.startObject();
             {
                 builder.field(Connector.CONFIGURATION_FIELD.getPreferredName(), configuration);
+                builder.field(VALUES_FIELD.getPreferredName(), configurationValues);
             }
             builder.endObject();
             return builder;
@@ -132,6 +152,7 @@ public class UpdateConnectorConfigurationAction {
             super.writeTo(out);
             out.writeString(connectorId);
             out.writeMap(configuration, StreamOutput::writeWriteable);
+            out.writeGenericMap(configurationValues);
         }
 
         @Override
@@ -139,12 +160,14 @@ public class UpdateConnectorConfigurationAction {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             Request request = (Request) o;
-            return Objects.equals(connectorId, request.connectorId) && Objects.equals(configuration, request.configuration);
+            return Objects.equals(connectorId, request.connectorId)
+                && Objects.equals(configuration, request.configuration)
+                && Objects.equals(configurationValues, request.configurationValues);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(connectorId, configuration);
+            return Objects.hash(connectorId, configuration, configurationValues);
         }
     }
 }

+ 88 - 3
x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java

@@ -43,8 +43,10 @@ import org.junit.Before;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -108,7 +110,7 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
         expectThrows(ResourceNotFoundException.class, () -> awaitDeleteConnector(connectorIdToDelete));
     }
 
-    public void testUpdateConnectorConfiguration() throws Exception {
+    public void testUpdateConnectorConfiguration_FullConfiguration() throws Exception {
         Connector connector = ConnectorTestUtils.getRandomConnector();
         String connectorId = randomUUID();
         DocWriteResponse resp = buildRequestAndAwaitPutConnector(connectorId, connector);
@@ -116,16 +118,99 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
 
         UpdateConnectorConfigurationAction.Request updateConfigurationRequest = new UpdateConnectorConfigurationAction.Request(
             connectorId,
-            connector.getConfiguration()
+            connector.getConfiguration(),
+            null
         );
 
         DocWriteResponse updateResponse = awaitUpdateConnectorConfiguration(updateConfigurationRequest);
         assertThat(updateResponse.status(), equalTo(RestStatus.OK));
 
-        // Configuration update is handled via painless script. ScriptEngine is mocked for unit tests.
+        // Full configuration update is handled via painless script. ScriptEngine is mocked for unit tests.
         // More comprehensive tests are defined in yamlRestTest.
     }
 
+    @AwaitsFix(bugUrl = "https://github.com/elastic/enterprise-search-team/issues/6351")
+    public void testUpdateConnectorConfiguration_PartialValuesUpdate() throws Exception {
+        Connector connector = ConnectorTestUtils.getRandomConnector();
+        String connectorId = randomUUID();
+        DocWriteResponse resp = buildRequestAndAwaitPutConnector(connectorId, connector);
+        assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK)));
+
+        Map<String, Object> connectorNewConfiguration = connector.getConfiguration()
+            .entrySet()
+            .stream()
+            .collect(
+                Collectors.toMap(
+                    Map.Entry::getKey,
+                    entry -> Map.of(ConnectorConfiguration.VALUE_FIELD.getPreferredName(), randomAlphaOfLengthBetween(3, 10))
+                )
+            );
+
+        UpdateConnectorConfigurationAction.Request updateConfigurationRequest = new UpdateConnectorConfigurationAction.Request(
+            connectorId,
+            null,
+            connectorNewConfiguration
+        );
+
+        DocWriteResponse updateResponse = awaitUpdateConnectorConfiguration(updateConfigurationRequest);
+        assertThat(updateResponse.status(), equalTo(RestStatus.OK));
+
+        Connector indexedConnector = awaitGetConnector(connectorId);
+
+        Map<String, ConnectorConfiguration> indexedConnectorConfiguration = indexedConnector.getConfiguration();
+
+        for (String configKey : indexedConnectorConfiguration.keySet()) {
+            assertThat(indexedConnectorConfiguration.get(configKey).getValue(), equalTo(connectorNewConfiguration.get(configKey)));
+        }
+    }
+
+    @AwaitsFix(bugUrl = "https://github.com/elastic/enterprise-search-team/issues/6351")
+    public void testUpdateConnectorConfiguration_PartialValuesUpdate_SelectedKeys() throws Exception {
+        Connector connector = ConnectorTestUtils.getRandomConnector();
+        String connectorId = randomUUID();
+        DocWriteResponse resp = buildRequestAndAwaitPutConnector(connectorId, connector);
+        assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK)));
+
+        Set<String> configKeys = connector.getConfiguration().keySet();
+
+        Set<String> keysToUpdate = new HashSet<>(randomSubsetOf(configKeys));
+
+        Map<String, Object> connectorNewConfigurationPartialValuesUpdate = keysToUpdate.stream()
+            .collect(
+                Collectors.toMap(
+                    key -> key,
+                    key -> Map.of(ConnectorConfiguration.VALUE_FIELD.getPreferredName(), randomAlphaOfLengthBetween(3, 10))
+                )
+            );
+
+        UpdateConnectorConfigurationAction.Request updateConfigurationRequest = new UpdateConnectorConfigurationAction.Request(
+            connectorId,
+            null,
+            connectorNewConfigurationPartialValuesUpdate
+        );
+
+        DocWriteResponse updateResponse = awaitUpdateConnectorConfiguration(updateConfigurationRequest);
+        assertThat(updateResponse.status(), equalTo(RestStatus.OK));
+
+        Connector indexedConnector = awaitGetConnector(connectorId);
+
+        Map<String, ConnectorConfiguration> indexedConnectorConfiguration = indexedConnector.getConfiguration();
+
+        for (String configKey : indexedConnectorConfiguration.keySet()) {
+            if (keysToUpdate.contains(configKey)) {
+                assertThat(
+                    indexedConnectorConfiguration.get(configKey).getValue(),
+                    equalTo(connectorNewConfigurationPartialValuesUpdate.get(configKey))
+                );
+            } else {
+                assertThat(
+                    indexedConnectorConfiguration.get(configKey).getValue(),
+                    equalTo(connector.getConfiguration().get(configKey).getValue())
+                );
+            }
+        }
+    }
+
     public void testUpdateConnectorPipeline() throws Exception {
         Connector connector = ConnectorTestUtils.getRandomConnector();
         String connectorId = randomUUID();

+ 9 - 1
x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorTestUtils.java

@@ -240,12 +240,20 @@ public final class ConnectorTestUtils {
 
     public static Map<String, ConnectorConfiguration> getRandomConnectorConfiguration() {
         Map<String, ConnectorConfiguration> configMap = new HashMap<>();
-        for (int i = 0; i < 3; i++) {
+        for (int i = 0; i < 5; i++) {
             configMap.put(randomAlphaOfLength(10), getRandomConnectorConfigurationField());
         }
         return configMap;
     }
 
+    public static Map<String, Object> getRandomConnectorConfigurationValues() {
+        Map<String, Object> configMap = new HashMap<>();
+        for (int i = 0; i < 5; i++) {
+            configMap.put(randomAlphaOfLength(10), randomFrom(randomAlphaOfLengthBetween(3, 10), randomInt(), randomBoolean()));
+        }
+        return configMap;
+    }
+
     public static Connector getRandomConnector() {
 
         return new Connector.Builder().setApiKeyId(randomFrom(new String[] { null, randomAlphaOfLength(10) }))

+ 5 - 1
x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorConfigurationActionRequestBWCSerializingTests.java

@@ -28,7 +28,11 @@ public class UpdateConnectorConfigurationActionRequestBWCSerializingTests extend
     @Override
     protected UpdateConnectorConfigurationAction.Request createTestInstance() {
         this.connectorId = randomUUID();
-        return new UpdateConnectorConfigurationAction.Request(connectorId, ConnectorTestUtils.getRandomConnectorConfiguration());
+        return new UpdateConnectorConfigurationAction.Request(
+            connectorId,
+            ConnectorTestUtils.getRandomConnectorConfiguration(),
+            ConnectorTestUtils.getRandomConnectorConfigurationValues()
+        );
     }
 
     @Override