Browse Source

HLRC support for Component Templates APIs (#54635)

* HLRC support for Component Templates

* hlrc

* hlrc

* merge fix

* removed unused import

* checkstyle fixes

* metaData -> metadata

* move to ClusterClient

* checkstyle fixes

* checkstyle fixes

* checkstyle fixes

* method in spec fixed

* PR comments

* PR comments

* PR comments

* unused imports fixed

* review comment

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Przemko Robakowski 5 years ago
parent
commit
ad8590e190

+ 115 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java

@@ -26,8 +26,14 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest
 import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsResponse;
 import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
 import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.client.cluster.RemoteInfoRequest;
 import org.elasticsearch.client.cluster.RemoteInfoResponse;
+import org.elasticsearch.client.indices.ComponentTemplatesExistRequest;
+import org.elasticsearch.client.indices.DeleteComponentTemplateRequest;
+import org.elasticsearch.client.indices.GetComponentTemplatesRequest;
+import org.elasticsearch.client.indices.GetComponentTemplatesResponse;
+import org.elasticsearch.client.indices.PutComponentTemplateRequest;
 import org.elasticsearch.rest.RestStatus;
 
 import java.io.IOException;
@@ -169,4 +175,113 @@ public final class ClusterClient {
         return restHighLevelClient.performRequestAsyncAndParseEntity(request, ClusterRequestConverters::remoteInfo, options,
                 RemoteInfoResponse::fromXContent, listener, singleton(RestStatus.REQUEST_TIMEOUT.getStatus()));
     }
+
+    /**
+     * Delete a component template using the Component Templates API
+     *
+     * @param req the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public AcknowledgedResponse deleteComponentTemplate(DeleteComponentTemplateRequest req, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(req, ClusterRequestConverters::deleteComponentTemplate,
+            options, AcknowledgedResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously delete a component template using the Component Templates API
+     *
+     * @param request  the request
+     * @param options  the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable deleteComponentTemplateAsync(DeleteComponentTemplateRequest request, RequestOptions options,
+                                                    ActionListener<AcknowledgedResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(request, ClusterRequestConverters::deleteComponentTemplate,
+            options, AcknowledgedResponse::fromXContent, listener, emptySet());
+    }
+
+    /**
+     * Puts a component template using the Component Templates API.
+     *
+     * @param putComponentTemplateRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public AcknowledgedResponse putComponentTemplate(PutComponentTemplateRequest putComponentTemplateRequest,
+                                                     RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(putComponentTemplateRequest, ClusterRequestConverters::putComponentTemplate,
+            options, AcknowledgedResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously puts a component template using the Component Templates API.
+     *
+     * @param putComponentTemplateRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable putComponentTemplateAsync(PutComponentTemplateRequest putComponentTemplateRequest,
+                                                 RequestOptions options, ActionListener<AcknowledgedResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(putComponentTemplateRequest,
+            ClusterRequestConverters::putComponentTemplate, options, AcknowledgedResponse::fromXContent, listener, emptySet());
+    }
+
+    /**
+     * Gets component templates using the Components Templates API
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param getComponentTemplatesRequest the request
+     * @return the response
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public GetComponentTemplatesResponse getComponentTemplate(GetComponentTemplatesRequest getComponentTemplatesRequest,
+                                                              RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(getComponentTemplatesRequest,
+            ClusterRequestConverters::getComponentTemplates, options, GetComponentTemplatesResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously gets component templates using the Components Templates API
+     * @param getComponentTemplatesRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable getComponentTemplateAsync(GetComponentTemplatesRequest getComponentTemplatesRequest, RequestOptions options,
+                                                 ActionListener<GetComponentTemplatesResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(getComponentTemplatesRequest,
+            ClusterRequestConverters::getComponentTemplates, options, GetComponentTemplatesResponse::fromXContent, listener, emptySet());
+    }
+
+    /**
+     * Uses the Component Templates API to determine if component templates exist
+     *
+     * @param componentTemplatesRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return true if any index templates in the request exist, false otherwise
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public boolean existsComponentTemplate(ComponentTemplatesExistRequest componentTemplatesRequest,
+                                           RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequest(componentTemplatesRequest,
+            ClusterRequestConverters::componentTemplatesExist, options, RestHighLevelClient::convertExistsResponse, emptySet());
+    }
+
+    /**
+     * Uses the Index Templates API to determine if index templates exist
+     * @param componentTemplatesRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion. The listener will be called with the value {@code true}
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable existsComponentTemplateAsync(ComponentTemplatesExistRequest componentTemplatesRequest,
+                                                    RequestOptions options,
+                                                    ActionListener<Boolean> listener) {
+
+        return restHighLevelClient.performRequestAsync(componentTemplatesRequest,
+            ClusterRequestConverters::componentTemplatesExist, options, RestHighLevelClient::convertExistsResponse, listener, emptySet());
+    }
 }

+ 59 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterRequestConverters.java

@@ -19,13 +19,19 @@
 
 package org.elasticsearch.client;
 
+import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
 import org.apache.http.client.methods.HttpPut;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
 import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest;
 import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
 import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.cluster.RemoteInfoRequest;
+import org.elasticsearch.client.indices.ComponentTemplatesExistRequest;
+import org.elasticsearch.client.indices.DeleteComponentTemplateRequest;
+import org.elasticsearch.client.indices.GetComponentTemplatesRequest;
+import org.elasticsearch.client.indices.PutComponentTemplateRequest;
 import org.elasticsearch.common.Strings;
 
 import java.io.IOException;
@@ -81,4 +87,57 @@ final class ClusterRequestConverters {
     static Request remoteInfo(RemoteInfoRequest remoteInfoRequest) {
         return new Request(HttpGet.METHOD_NAME, "/_remote/info");
     }
+
+    static Request putComponentTemplate(PutComponentTemplateRequest putComponentTemplateRequest) throws IOException {
+        String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_component_template")
+            .addPathPart(putComponentTemplateRequest.name()).build();
+        Request request = new Request(HttpPut.METHOD_NAME, endpoint);
+        RequestConverters.Params params = new RequestConverters.Params();
+        params.withMasterTimeout(putComponentTemplateRequest.masterNodeTimeout());
+        if (putComponentTemplateRequest.create()) {
+            params.putParam("create", Boolean.TRUE.toString());
+        }
+        if (Strings.hasText(putComponentTemplateRequest.cause())) {
+            params.putParam("cause", putComponentTemplateRequest.cause());
+        }
+        request.addParameters(params.asMap());
+        request.setEntity(RequestConverters.createEntity(putComponentTemplateRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
+        return request;
+    }
+
+    static Request getComponentTemplates(GetComponentTemplatesRequest getComponentTemplatesRequest){
+        final String endpoint = new RequestConverters.EndpointBuilder()
+            .addPathPartAsIs("_component_template")
+            .addPathPart(getComponentTemplatesRequest.name())
+            .build();
+        final Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+        final RequestConverters.Params params = new RequestConverters.Params();
+        params.withLocal(getComponentTemplatesRequest.isLocal());
+        params.withMasterTimeout(getComponentTemplatesRequest.getMasterNodeTimeout());
+        request.addParameters(params.asMap());
+        return request;
+    }
+
+    static Request componentTemplatesExist(ComponentTemplatesExistRequest componentTemplatesRequest) {
+        final String endpoint = new RequestConverters.EndpointBuilder()
+            .addPathPartAsIs("_component_template")
+            .addPathPart(componentTemplatesRequest.name())
+            .build();
+        final Request request = new Request(HttpHead.METHOD_NAME, endpoint);
+        final RequestConverters.Params params = new RequestConverters.Params();
+        params.withLocal(componentTemplatesRequest.isLocal());
+        params.withMasterTimeout(componentTemplatesRequest.getMasterNodeTimeout());
+        request.addParameters(params.asMap());
+        return request;
+    }
+
+    static Request deleteComponentTemplate(DeleteComponentTemplateRequest deleteComponentTemplateRequest) {
+        String name = deleteComponentTemplateRequest.getName();
+        String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_component_template").addPathPart(name).build();
+        Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
+        RequestConverters.Params params = new RequestConverters.Params();
+        params.withMasterTimeout(deleteComponentTemplateRequest.masterNodeTimeout());
+        request.addParameters(params.asMap());
+        return request;
+    }
 }

+ 40 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/indices/ComponentTemplatesExistRequest.java

@@ -0,0 +1,40 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.indices;
+
+import org.elasticsearch.common.Strings;
+
+/**
+ * A request to check for the existence of component templates
+ */
+public class ComponentTemplatesExistRequest extends GetComponentTemplatesRequest {
+
+    /**
+     * Create a request to check for the existence of component template. Name must be provided
+     *
+     * @param name the name of template to check for the existence of
+     */
+    public ComponentTemplatesExistRequest(String name) {
+        super(name);
+        if (Strings.isNullOrEmpty(name)) {
+            throw new IllegalArgumentException("must provide component template name");
+        }
+    }
+}

+ 35 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/indices/DeleteComponentTemplateRequest.java

@@ -0,0 +1,35 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.indices;
+
+import org.elasticsearch.client.TimedRequest;
+
+public class DeleteComponentTemplateRequest extends TimedRequest {
+
+    private final String name;
+
+    public DeleteComponentTemplateRequest(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 80 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesRequest.java

@@ -0,0 +1,80 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.indices;
+
+import org.elasticsearch.client.TimedRequest;
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.unit.TimeValue;
+
+/**
+ * A request to read the content of component templates
+ */
+public class GetComponentTemplatesRequest implements Validatable {
+
+    private final String name;
+
+    private TimeValue masterNodeTimeout = TimedRequest.DEFAULT_MASTER_NODE_TIMEOUT;
+    private boolean local = false;
+
+    /**
+     * Create a request to read the content of component template. If no template name is provided, all templates will
+     * be read
+     *
+     * @param name the name of template to read
+     */
+    public GetComponentTemplatesRequest(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return the name of component template this request is requesting
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * @return the timeout for waiting for the master node to respond
+     */
+    public TimeValue getMasterNodeTimeout() {
+        return masterNodeTimeout;
+    }
+
+    public void setMasterNodeTimeout(@Nullable TimeValue masterNodeTimeout) {
+        this.masterNodeTimeout = masterNodeTimeout;
+    }
+
+    public void setMasterNodeTimeout(String masterNodeTimeout) {
+        final TimeValue timeValue = TimeValue.parseTimeValue(masterNodeTimeout, getClass().getSimpleName() + ".masterNodeTimeout");
+        setMasterNodeTimeout(timeValue);
+    }
+
+    /**
+     * @return true if this request is to read from the local cluster state, rather than the master node - false otherwise
+     */
+    public boolean isLocal() {
+        return local;
+    }
+
+    public void setLocal(boolean local) {
+        this.local = local;
+    }
+}

+ 108 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesResponse.java

@@ -0,0 +1,108 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client.indices;
+
+import org.elasticsearch.cluster.metadata.ComponentTemplate;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+
+public class GetComponentTemplatesResponse {
+
+    public static final ParseField NAME = new ParseField("name");
+    public static final ParseField COMPONENT_TEMPLATES = new ParseField("component_templates");
+    public static final ParseField COMPONENT_TEMPLATE = new ParseField("component_template");
+
+    @SuppressWarnings("unchecked")
+    private static final ConstructingObjectParser<Map<String, ComponentTemplate>, Void> PARSER =
+        new ConstructingObjectParser<>("component_templates", false,
+            a -> ((List<NamedComponentTemplate>) a[0]).stream().collect(Collectors.toMap(n -> n.name, n -> n.componentTemplate,
+                (n1, n2) -> n1, LinkedHashMap::new)));
+
+    private static final ConstructingObjectParser<NamedComponentTemplate, Void> INNER_PARSER =
+        new ConstructingObjectParser<>("named_component_template", false,
+            a -> new NamedComponentTemplate((String) a[0], (ComponentTemplate) a[1]));
+
+    static {
+        INNER_PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
+        INNER_PARSER.declareObject(ConstructingObjectParser.constructorArg(), ComponentTemplate.PARSER, COMPONENT_TEMPLATE);
+        PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), INNER_PARSER, COMPONENT_TEMPLATES);
+    }
+
+    private static class NamedComponentTemplate {
+        String name;
+        ComponentTemplate componentTemplate;
+
+        private NamedComponentTemplate(String name, ComponentTemplate componentTemplate) {
+            this.name = name;
+            this.componentTemplate = componentTemplate;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "GetIndexTemplatesResponse [indexTemplates=" + componentTemplates + "]";
+    }
+
+    private final Map<String, ComponentTemplate> componentTemplates;
+
+    GetComponentTemplatesResponse(Map<String, ComponentTemplate> componentTemplates) {
+        this.componentTemplates = Collections.unmodifiableMap(new LinkedHashMap<>(componentTemplates));
+    }
+
+    public Map<String, ComponentTemplate> getComponentTemplates() {
+        return componentTemplates;
+    }
+
+
+    public static GetComponentTemplatesResponse fromXContent(XContentParser parser) throws IOException {
+        return new GetComponentTemplatesResponse(PARSER.apply(parser, null));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(componentTemplates);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        GetComponentTemplatesResponse other = (GetComponentTemplatesResponse) obj;
+        return Objects.equals(componentTemplates, other.componentTemplates);
+    }
+
+
+}

+ 100 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/indices/PutComponentTemplateRequest.java

@@ -0,0 +1,100 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client.indices;
+
+import org.elasticsearch.client.TimedRequest;
+import org.elasticsearch.cluster.metadata.ComponentTemplate;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+/**
+ * A request to create an component template.
+ */
+public class PutComponentTemplateRequest extends TimedRequest implements ToXContentObject {
+
+    private String name;
+
+    private String cause = "";
+
+    private boolean create;
+
+    private ComponentTemplate componentTemplate;
+
+    /**
+     * Sets the name of the component template.
+     */
+    public PutComponentTemplateRequest name(String name) {
+        if (Strings.isNullOrEmpty(name)) {
+            throw new IllegalArgumentException("name cannot be null or empty");
+        }
+        this.name = name;
+        return this;
+    }
+
+    /**
+     * The name of the component template.
+     */
+    public String name() {
+        return this.name;
+    }
+
+    /**
+     * Set to {@code true} to force only creation, not an update of an component template. If it already
+     * exists, it will fail with an {@link IllegalArgumentException}.
+     */
+    public PutComponentTemplateRequest create(boolean create) {
+        this.create = create;
+        return this;
+    }
+
+    public boolean create() {
+        return create;
+    }
+
+    /**
+     * The component template to create.
+     */
+    public PutComponentTemplateRequest componentTemplate(ComponentTemplate componentTemplate) {
+        this.componentTemplate = componentTemplate;
+        return this;
+    }
+
+    /**
+     * The cause for this component template creation.
+     */
+    public PutComponentTemplateRequest cause(String cause) {
+        this.cause = cause;
+        return this;
+    }
+
+    public String cause() {
+        return this.cause;
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        if (componentTemplate != null) {
+            componentTemplate.toXContent(builder, params);
+        }
+        return builder;
+    }
+}

+ 55 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java

@@ -21,20 +21,31 @@ package org.elasticsearch.client;
 
 import org.apache.http.util.EntityUtils;
 import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.ElasticsearchStatusException;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
 import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest;
 import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsResponse;
 import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
 import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.client.cluster.RemoteConnectionInfo;
 import org.elasticsearch.client.cluster.RemoteInfoRequest;
 import org.elasticsearch.client.cluster.RemoteInfoResponse;
 import org.elasticsearch.client.cluster.SniffModeInfo;
+import org.elasticsearch.client.indices.ComponentTemplatesExistRequest;
+import org.elasticsearch.client.indices.DeleteComponentTemplateRequest;
+import org.elasticsearch.client.indices.GetComponentTemplatesRequest;
+import org.elasticsearch.client.indices.GetComponentTemplatesResponse;
+import org.elasticsearch.client.indices.PutComponentTemplateRequest;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.cluster.health.ClusterIndexHealth;
 import org.elasticsearch.cluster.health.ClusterShardHealth;
+import org.elasticsearch.cluster.metadata.AliasMetadata;
+import org.elasticsearch.cluster.metadata.ComponentTemplate;
+import org.elasticsearch.cluster.metadata.Template;
 import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
+import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.ByteSizeUnit;
 import org.elasticsearch.common.unit.TimeValue;
@@ -342,4 +353,48 @@ public class ClusterClientIT extends ESRestHighLevelClientTestCase {
         assertThat(sniffModeInfo.getSeedNodes(), equalTo(seeds));
     }
 
+    public void testComponentTemplates() throws Exception {
+        String templateName = "my-template";
+        Settings settings = Settings.builder().put("index.number_of_shards", 1).build();
+        CompressedXContent mappings = new CompressedXContent("{\"properties\":{\"host_name\":{\"type\":\"keyword\"}}}");
+        AliasMetadata alias = AliasMetadata.builder("alias").writeIndex(true).build();
+        Template template = new Template(settings, mappings, Map.of("alias", alias));
+        ComponentTemplate componentTemplate = new ComponentTemplate(template, 1L, new HashMap<>());
+        PutComponentTemplateRequest putComponentTemplateRequest =
+            new PutComponentTemplateRequest().name(templateName).create(true).componentTemplate(componentTemplate);
+
+        AcknowledgedResponse response = execute(putComponentTemplateRequest,
+            highLevelClient().cluster()::putComponentTemplate, highLevelClient().cluster()::putComponentTemplateAsync);
+        assertThat(response.isAcknowledged(), equalTo(true));
+
+        ComponentTemplatesExistRequest componentTemplatesExistRequest = new ComponentTemplatesExistRequest(templateName);
+        boolean exist = execute(componentTemplatesExistRequest,
+            highLevelClient().cluster()::existsComponentTemplate, highLevelClient().cluster()::existsComponentTemplateAsync);
+
+        assertTrue(exist);
+
+        GetComponentTemplatesRequest getComponentTemplatesRequest = new GetComponentTemplatesRequest(templateName);
+        GetComponentTemplatesResponse getResponse = execute(getComponentTemplatesRequest,
+            highLevelClient().cluster()::getComponentTemplate, highLevelClient().cluster()::getComponentTemplateAsync);
+
+        assertThat(getResponse.getComponentTemplates().size(), equalTo(1));
+        assertThat(getResponse.getComponentTemplates().containsKey(templateName), equalTo(true));
+        assertThat(getResponse.getComponentTemplates().get(templateName), equalTo(componentTemplate));
+
+        DeleteComponentTemplateRequest deleteComponentTemplateRequest = new DeleteComponentTemplateRequest(templateName);
+        response = execute(deleteComponentTemplateRequest, highLevelClient().cluster()::deleteComponentTemplate,
+            highLevelClient().cluster()::deleteComponentTemplateAsync);
+        assertThat(response.isAcknowledged(), equalTo(true));
+
+        ElasticsearchStatusException statusException = expectThrows(ElasticsearchStatusException.class,
+            () -> execute(getComponentTemplatesRequest,
+                highLevelClient().cluster()::getComponentTemplate, highLevelClient().cluster()::getComponentTemplateAsync));
+
+        assertThat(statusException.status(), equalTo(RestStatus.NOT_FOUND));
+
+        exist = execute(componentTemplatesExistRequest,
+            highLevelClient().cluster()::existsComponentTemplate, highLevelClient().cluster()::existsComponentTemplateAsync);
+
+        assertFalse(exist);
+    }
 }

+ 0 - 3
client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

@@ -858,9 +858,6 @@ public class RestHighLevelClientTests extends ESTestCase {
             "indices.put_alias",
             "render_search_template",
             "scripts_painless_execute",
-            "cluster.put_component_template",
-            "cluster.get_component_template",
-            "cluster.delete_component_template",
             "indices.create_data_stream",
             "indices.get_data_streams",
             "indices.delete_data_stream",

+ 131 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/indices/GetComponentTemplatesResponseTests.java

@@ -0,0 +1,131 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.indices;
+
+import org.elasticsearch.cluster.metadata.AliasMetadata;
+import org.elasticsearch.cluster.metadata.ComponentTemplate;
+import org.elasticsearch.cluster.metadata.Template;
+import org.elasticsearch.common.compress.CompressedXContent;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester;
+
+public class GetComponentTemplatesResponseTests extends ESTestCase {
+
+    public void testFromXContent() throws Exception {
+        xContentTester(
+            this::createParser,
+            GetComponentTemplatesResponseTests::createTestInstance,
+            GetComponentTemplatesResponseTests::toXContent,
+            GetComponentTemplatesResponse::fromXContent)
+            .supportsUnknownFields(true)
+            .randomFieldsExcludeFilter(a -> true)
+            .test();
+    }
+
+    private static GetComponentTemplatesResponse createTestInstance() {
+        Map<String, ComponentTemplate> templates = new HashMap<>();
+        if (randomBoolean()) {
+            int count = randomInt(10);
+            for (int i = 0; i < count; i++) {
+                templates.put(randomAlphaOfLength(10), randomTemplate());
+            }
+        }
+        return new GetComponentTemplatesResponse(templates);
+    }
+
+    private static void toXContent(GetComponentTemplatesResponse response, XContentBuilder builder) throws IOException {
+        builder.startObject();
+        builder.startArray("component_templates");
+        for (Map.Entry<String, ComponentTemplate> e : response.getComponentTemplates().entrySet()) {
+            builder.startObject();
+            builder.field("name", e.getKey());
+            builder.field("component_template");
+            e.getValue().toXContent(builder, null);
+            builder.endObject();
+        }
+        builder.endArray();
+        builder.endObject();
+    }
+
+    private static ComponentTemplate randomTemplate() {
+        Settings settings = null;
+        CompressedXContent mappings = null;
+        Map<String, AliasMetadata> aliases = null;
+        if (randomBoolean()) {
+            settings = randomSettings();
+        }
+        if (randomBoolean()) {
+            mappings = randomMappings();
+        }
+        if (randomBoolean()) {
+            aliases = randomAliases();
+        }
+        Template template = new Template(settings, mappings, aliases);
+
+        Map<String, Object> meta = null;
+        if (randomBoolean()) {
+            meta = randomMeta();
+        }
+        return new ComponentTemplate(template, randomBoolean() ? null : randomNonNegativeLong(), meta);
+    }
+
+    private static Map<String, AliasMetadata> randomAliases() {
+        String aliasName = randomAlphaOfLength(5);
+        AliasMetadata aliasMeta = AliasMetadata.builder(aliasName)
+            .filter(Collections.singletonMap(randomAlphaOfLength(2), randomAlphaOfLength(2)))
+            .routing(randomBoolean() ? null : randomAlphaOfLength(3))
+            .isHidden(randomBoolean() ? null : randomBoolean())
+            .writeIndex(randomBoolean() ? null : randomBoolean())
+            .build();
+        return Collections.singletonMap(aliasName, aliasMeta);
+    }
+
+    private static CompressedXContent randomMappings() {
+        try {
+            return new CompressedXContent("{\"" + randomAlphaOfLength(3) + "\":\"" + randomAlphaOfLength(7) + "\"}");
+        } catch (IOException e) {
+            fail("got an IO exception creating fake mappings: " + e);
+            return null;
+        }
+    }
+
+    private static Settings randomSettings() {
+        return Settings.builder()
+            .put(randomAlphaOfLength(4), randomAlphaOfLength(10))
+            .build();
+    }
+
+    private static Map<String, Object> randomMeta() {
+        if (randomBoolean()) {
+            return Collections.singletonMap(randomAlphaOfLength(4), randomAlphaOfLength(4));
+        } else {
+            return Collections.singletonMap(randomAlphaOfLength(5),
+                Collections.singletonMap(randomAlphaOfLength(4), randomAlphaOfLength(4)));
+        }
+    }
+}

+ 35 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/cluster.exists_component_template.json

@@ -0,0 +1,35 @@
+{
+  "cluster.exists_component_template":{
+    "documentation":{
+      "url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-component-templates.html",
+      "description":"Returns information about whether a particular component template exist"
+    },
+    "stability":"stable",
+    "url":{
+      "paths":[
+        {
+          "path":"/_component_template/{name}",
+          "methods":[
+            "HEAD"
+          ],
+          "parts":{
+            "name":{
+              "type":"string",
+              "description":"The name of the template"
+            }
+          }
+        }
+      ]
+    },
+    "params":{
+      "master_timeout":{
+        "type":"time",
+        "description":"Explicit operation timeout for connection to master node"
+      },
+      "local":{
+        "type":"boolean",
+        "description":"Return local information, do not retrieve the state from master node (default: false)"
+      }
+    }
+  }
+}

+ 1 - 1
server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java

@@ -47,7 +47,7 @@ public class ComponentTemplate extends AbstractDiffable<ComponentTemplate> imple
     private static final ParseField METADATA = new ParseField("_meta");
 
     @SuppressWarnings("unchecked")
-    private static final ConstructingObjectParser<ComponentTemplate, Void> PARSER =
+    public static final ConstructingObjectParser<ComponentTemplate, Void> PARSER =
         new ConstructingObjectParser<>("component_template", false,
             a -> new ComponentTemplate((Template) a[0], (Long) a[1], (Map<String, Object>) a[2]));