Repository: dadoonet/elasticsearch-beyonder
Pull request #165 (2021-06-01 15:46:47)

Managing Index Templates V2
Managing index templates (aka templates V2)
-------------------------------------------

Since version 7.13, the [new index template management API](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html) is supported.
It allows to define both [component templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-component-template.html)
and [index templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-template.html).

### Component templates

To define component templates, you can create json files within the `elasticsearch/_component_templates/` dir.

Let's first create a `elasticsearch/_component_templates/component1.json`:

```json
{
  "template": {
    "mappings": {
      "properties": {
        "@timestamp": {
          "type": "date"
        }
      }
    }
  }
}
```

Then a second component template as `elasticsearch/_component_templates/component2.json`:

```json
{
  "template": {
    "mappings": {
      "runtime": {
        "day_of_week": {
          "type": "keyword",
          "script": {
            "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
          }
        }
      }
    }
  }
}
```

When Beyonder starts, it will create 2 component templates into elasticsearch, named respectively `component1`
and `component2`.

By default, Beyonder will not overwrite a component template if it already exists.
This can be overridden by setting `force` to `true` in the expanded factory method
`ElasticsearchBeyonder.start()`.

### Index templates

To define index templates, you can create json files within the `elasticsearch/_index_templates/` dir.

Let's create a `elasticsearch/_index_templates/template_1.json`:

```json
{
  "index_patterns": ["te*", "bar*"],
  "template": {
    "settings": {
      "number_of_shards": 1
    },
    "mappings": {
      "_source": {
        "enabled": true
      },
      "properties": {
        "host_name": {
          "type": "keyword"
        },
        "created_at": {
          "type": "date",
          "format": "EEE MMM dd HH:mm:ss Z yyyy"
        }
      }
    },
    "aliases": {
      "mydata": { }
    }
  },
  "priority": 500,
  "composed_of": ["component1", "component2"],
  "version": 3,
  "_meta": {
    "description": "my custom"
  }
}
```

When Beyonder starts, it will create the index templates named `template_1` into elasticsearch.
Note that this index template references 2 component templates that must be available before Beyonder starts
or defined within the `component_templates` dir as we saw just before.

By default, Beyonder will not overwrite an index template if it already exists.
This can be overridden by setting `force` to `true` in the expanded factory method
`ElasticsearchBeyonder.start()`.


Patch:
diff --git a/README.md b/README.md
index d49803a..8e24c6b 100644
--- a/README.md
+++ b/README.md
@@ -204,8 +204,111 @@ And you can create `elasticsearch/twitter/_update_mapping.json`:
 This will change the `search_analyzer` for the `message` field and will add a new field named `bar`.
 All other existing fields (like `foo` in the previous example) won't be changed.
 
-Managing templates
-------------------
+Managing index templates (aka templates V2)
+-------------------------------------------
+
+Since version 7.13, the [new index template management API](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html) is supported.
+It allows to define both [component templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-component-template.html)
+and [index templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-template.html).
+
+### Component templates
+
+To define component templates, you can create json files within the `elasticsearch/_component_templates/` dir.
+
+Let's first create a `elasticsearch/_component_templates/component1.json`:
+
+```json
+{
+  "template": {
+    "mappings": {
+      "properties": {
+        "@timestamp": {
+          "type": "date"
+        }
+      }
+    }
+  }
+}
+```
+
+Then a second component template as `elasticsearch/_component_templates/component2.json`:
+
+```json
+{
+  "template": {
+    "mappings": {
+      "runtime": {
+        "day_of_week": {
+          "type": "keyword",
+          "script": {
+            "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
+          }
+        }
+      }
+    }
+  }
+}
+```
+
+When Beyonder starts, it will create 2 component templates into elasticsearch, named respectively `component1`
+and `component2`.
+
+By default, Beyonder will not overwrite a component template if it already exists.
+This can be overridden by setting `force` to `true` in the expanded factory method
+`ElasticsearchBeyonder.start()`.
+
+### Index templates
+
+To define index templates, you can create json files within the `elasticsearch/_index_templates/` dir.
+
+Let's create a `elasticsearch/_index_templates/template_1.json`:
+
+```json
+{
+  "index_patterns": ["te*", "bar*"],
+  "template": {
+    "settings": {
+      "number_of_shards": 1
+    },
+    "mappings": {
+      "_source": {
+        "enabled": true
+      },
+      "properties": {
+        "host_name": {
+          "type": "keyword"
+        },
+        "created_at": {
+          "type": "date",
+          "format": "EEE MMM dd HH:mm:ss Z yyyy"
+        }
+      }
+    },
+    "aliases": {
+      "mydata": { }
+    }
+  },
+  "priority": 500,
+  "composed_of": ["component1", "component2"],
+  "version": 3,
+  "_meta": {
+    "description": "my custom"
+  }
+}
+```
+
+When Beyonder starts, it will create the index templates named `template_1` into elasticsearch. 
+Note that this index template references 2 component templates that must be available before Beyonder starts
+or defined within the `component_templates` dir as we saw just before.
+
+By default, Beyonder will not overwrite an index template if it already exists.
+This can be overridden by setting `force` to `true` in the expanded factory method
+`ElasticsearchBeyonder.start()`.
+
+Managing legacy templates (deprecated)
+--------------------------------------
+
+This method is deprecated as Elasticsearch has deprecated [legacy templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html).
 
 Sometimes it's useful to define a template mapping that will automatically be applied to new indices created. 
 
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/ElasticsearchBeyonder.java b/src/main/java/fr/pilato/elasticsearch/tools/ElasticsearchBeyonder.java
index 5fac92e..ab9f423 100644
--- a/src/main/java/fr/pilato/elasticsearch/tools/ElasticsearchBeyonder.java
+++ b/src/main/java/fr/pilato/elasticsearch/tools/ElasticsearchBeyonder.java
@@ -19,10 +19,8 @@
 
 package fr.pilato.elasticsearch.tools;
 
-import fr.pilato.elasticsearch.tools.SettingsFinder.Defaults;
-import fr.pilato.elasticsearch.tools.index.IndexFinder;
-import fr.pilato.elasticsearch.tools.template.TemplateFinder;
-import fr.pilato.elasticsearch.tools.pipeline.PipelineFinder;
+import fr.pilato.elasticsearch.tools.util.SettingsFinder.Defaults;
+import fr.pilato.elasticsearch.tools.util.ResourceList;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.RestClient;
 import org.slf4j.Logger;
@@ -31,11 +29,13 @@ import org.slf4j.LoggerFactory;
 import java.util.Collection;
 import java.util.List;
 
-import static fr.pilato.elasticsearch.tools.index.IndexElasticsearchUpdater.createIndex;
-import static fr.pilato.elasticsearch.tools.index.IndexElasticsearchUpdater.updateMapping;
-import static fr.pilato.elasticsearch.tools.index.IndexElasticsearchUpdater.updateSettings;
-import static fr.pilato.elasticsearch.tools.template.TemplateElasticsearchUpdater.createTemplate;
-import static fr.pilato.elasticsearch.tools.pipeline.PipelineElasticsearchUpdater.createPipeline;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchComponentTemplateUpdater.createComponentTemplate;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexUpdater.createIndex;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexUpdater.updateMapping;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexUpdater.updateSettings;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexTemplateUpdater.createIndexTemplate;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchPipelineUpdater.createPipeline;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchTemplateUpdater.createTemplate;
 
 /**
  * By default, indexes are created with their default Elasticsearch settings. You can specify
@@ -106,20 +106,34 @@ public class ElasticsearchBeyonder {
 	public static void start(RestClient client, String root, boolean merge, boolean force) throws Exception {
 		logger.info("starting automatic settings/mappings discovery");
 
-		// create templates
-		List<String> templateNames = TemplateFinder.findTemplates(root);
+		// create legacy templates
+		List<String> templateNames = ResourceList.getResourceNames(root, Defaults.TemplateDir);
 		for (String templateName : templateNames) {
+			logger.warn("Legacy Templates are deprecated in Elasticsearch. Switch to Index Templates instead by using {}/{}{}",
+					Defaults.IndexTemplatesDir, templateName, Defaults.JsonFileExtension);
 			createTemplate(client, root, templateName, force);
 		}
 
+		// create component templates
+		List<String> componentTemplates = ResourceList.getResourceNames(root, Defaults.ComponentTemplatesDir);
+		for (String componentTemplateName : componentTemplates) {
+			createComponentTemplate(client, root, componentTemplateName, force);
+		}
+
+		// create index templates
+		List<String> indexTemplateNames = ResourceList.getResourceNames(root, Defaults.IndexTemplatesDir);
+		for (String indexTemplateName : indexTemplateNames) {
+			createIndexTemplate(client, root, indexTemplateName, force);
+		}
+
 		// create pipelines
-		List<String> pipelineNames = PipelineFinder.findPipelines(root);
+		List<String> pipelineNames = ResourceList.getResourceNames(root, Defaults.PipelineDir);
 		for (String pipelineName : pipelineNames) {
 			createPipeline(client, root, pipelineName, force);
 		}
 
 		// create indices
-		Collection<String> indexNames = IndexFinder.findIndexNames(root);
+		Collection<String> indexNames = ResourceList.findIndexNames(root);
 		for (String indexName : indexNames) {
 			createIndex(client, root, indexName, force);
 			updateSettings(client, root, indexName);
@@ -157,13 +171,13 @@ public class ElasticsearchBeyonder {
 		boolean force = Defaults.ForceCreation;
 
 		// create templates
-		List<String> templateNames = TemplateFinder.findTemplates(root);
+		List<String> templateNames = ResourceList.getResourceNames(root, Defaults.TemplateDir);
 		for (String templateName : templateNames) {
 			createTemplate(client, root, templateName, force);
 		}
 
 		// create indices
-		Collection<String> indexNames = IndexFinder.findIndexNames(root);
+		Collection<String> indexNames = ResourceList.findIndexNames(root);
 		for (String indexName : indexNames) {
 			createIndex(client, root, indexName, force);
 			updateSettings(client, root, indexName);
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/index/IndexFinder.java b/src/main/java/fr/pilato/elasticsearch/tools/index/IndexFinder.java
deleted file mode 100644
index 0abc3d9..0000000
--- a/src/main/java/fr/pilato/elasticsearch/tools/index/IndexFinder.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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 fr.pilato.elasticsearch.tools.index;
-
-import fr.pilato.elasticsearch.tools.ResourceList;
-import fr.pilato.elasticsearch.tools.SettingsFinder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class IndexFinder extends SettingsFinder {
-    private static final Logger logger = LoggerFactory.getLogger(IndexFinder.class);
-
-    /**
-     * Find all indices existing in a given default classpath dir
-     * @return a list of indices
-     * @throws IOException if connection with elasticsearch is failing
-     * @throws URISyntaxException this should not happen
-     */
-    public static List<String> findIndexNames() throws IOException, URISyntaxException {
-        return findIndexNames(Defaults.ConfigDir);
-    }
-
-    /**
-     * Find all indices existing in a given classpath dir
-     * @param root dir within the classpath
-     * @return a list of indices
-     * @throws IOException if connection with elasticsearch is failing
-     * @throws URISyntaxException this should not happen
-     */
-    public static List<String> findIndexNames(final String root) throws IOException, URISyntaxException {
-        if (root == null) {
-            return findIndexNames();
-        }
-
-        logger.debug("Looking for indices in classpath under [{}].", root);
-
-        final List<String> indexNames = new ArrayList<>();
-        final Set<String> keys = new HashSet<>();
-        String[] resources = ResourceList.getResources(root + "/"); // "es/" or "a/b/c/"
-        for (String resource : resources) {
-            if (!resource.isEmpty()) {
-                logger.trace(" - resource [{}].", resource);
-                String key;
-                if (resource.contains("/")) {
-                    key = resource.substring(0, resource.indexOf("/"));
-                } else {
-                    key = resource;
-                }
-                if (!key.equals(Defaults.TemplateDir) && !key.equals(Defaults.PipelineDir) && !keys.contains(key)) {
-                    logger.trace(" - found [{}].", key);
-                    keys.add(key);
-                    indexNames.add(key);
-                }
-            }
-        }
-
-        return indexNames;
-    }
-}
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/index/IndexSettingsReader.java b/src/main/java/fr/pilato/elasticsearch/tools/index/IndexSettingsReader.java
deleted file mode 100644
index e9b8974..0000000
--- a/src/main/java/fr/pilato/elasticsearch/tools/index/IndexSettingsReader.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Licensed to David Pilato (the "Author") under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. Author 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 fr.pilato.elasticsearch.tools.index;
-
-import fr.pilato.elasticsearch.tools.SettingsFinder.Defaults;
-import fr.pilato.elasticsearch.tools.SettingsReader;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-/**
- * Manage elasticsearch index setting files
- * @author David Pilato
- */
-public class IndexSettingsReader extends SettingsReader {
-
-	private static final Logger logger = LoggerFactory.getLogger(IndexSettingsReader.class);
-
-	/**
-	 * Read index settings
-	 * @param root dir within the classpath
-	 * @param index index name
-	 * @param jsonFile json file to read
-	 * @return Settings
-	 */
-	public static String readFile(String root, String index, String jsonFile) {
-		logger.trace("Reading [{}] for [{}] in [{}]...", jsonFile, index, root);
-		String settingsFile = root + "/" + index + "/" + jsonFile;
-		return readFileFromClasspath(settingsFile);
-	}
-
-	/**
-	 * Read index settings
-	 * @param root dir within the classpath
-	 * @param index index name
-     * @return Settings
-     * @throws IOException if connection with elasticsearch is failing
-     */
-	public static String readSettings(String root, String index) throws IOException {
-		if (root == null) {
-			return readSettings(index);
-		}
-		return readFile(root, index, Defaults.IndexSettingsFileName);
-	}
-
-	/**
-	 * Read index settings in default classpath dir
-	 * @param index index name
-     * @return Settings
-     * @throws IOException if connection with elasticsearch is failing
-	 */
-	public static String readSettings(String index) throws IOException {
-		return readSettings(Defaults.ConfigDir, index);
-	}
-
-	/**
-	 * Read index settings
-	 * @param root dir within the classpath
-	 * @param index index name
-     * @return Update Settings
-	 */
-	public static String readUpdateSettings(String root, String index) {
-		return readFile(root, index, Defaults.UpdateIndexSettingsFileName);
-	}
-
-	/**
-	 * Read index settings in default classpath dir
-	 * @param index index name
-     * @return Update Settings
-     */
-	public static String readUpdateSettings(String index) {
-		return readUpdateSettings(Defaults.ConfigDir, index);
-	}
-
-	/**
-	 * Read index mapping
-	 * @param root dir within the classpath
-	 * @param index index name
-     * @return Update Mapping
-	 */
-	public static String readUpdateMapping(String root, String index) {
-		return readFile(root, index, Defaults.UpdateIndexMappingFileName);
-	}
-
-	/**
-	 * Read index mapping in default classpath dir
-	 * @param index index name
-     * @return Update Mapping
-     */
-	public static String readUpdateMapping(String index) {
-		return readUpdateMapping(Defaults.ConfigDir, index);
-	}
-}
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineFinder.java b/src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineFinder.java
deleted file mode 100644
index 578940d..0000000
--- a/src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineFinder.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to David Pilato (the "Author") under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. Author 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 fr.pilato.elasticsearch.tools.pipeline;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import fr.pilato.elasticsearch.tools.ResourceList;
-import fr.pilato.elasticsearch.tools.SettingsFinder;
-import fr.pilato.elasticsearch.tools.template.TemplateFinder;
-
-/**
- * Findes ingest pipelines on the classpath.
- * 
- * @author hjk181
- *
- */
-public class PipelineFinder extends SettingsFinder {
-
-    private static final Logger logger = LoggerFactory.getLogger(TemplateFinder.class);
-
-    /**
-     * Find all pipelines in default classpath dir
-     * 
-     * @return a list of pipelines
-     * @throws IOException if connection with elasticsearch is failing
-     * @throws URISyntaxException this should not happen
-     */
-    public static List<String> findPipelines() throws IOException, URISyntaxException {
-        return findPipelines(Defaults.ConfigDir);
-    }
-
-    /**
-     * Find all pipelines
-     * 
-     * @param root dir within the classpath
-     * @return a list of pipelines
-     * @throws IOException if connection with elasticsearch is failing
-     * @throws URISyntaxException this should not happen
-     */
-    public static List<String> findPipelines(String root) throws IOException, URISyntaxException {
-        if (root == null) {
-            return findPipelines();
-        }
-
-        logger.debug("Looking for pipelines in classpath under [{}].", root);
-
-        final List<String> pipelineNames = new ArrayList<>();
-        String[] resources = ResourceList.getResources(root + "/" + Defaults.PipelineDir + "/"); // "es/_pipeline/"
-        for (String resource : resources) {
-            if (!resource.isEmpty()) {
-                String withoutIndex = resource.substring(resource.indexOf("/") + 1);
-                String pipeline = withoutIndex.substring(0, withoutIndex.indexOf(Defaults.JsonFileExtension));
-                logger.trace(" - found [{}].", pipeline);
-                pipelineNames.add(pipeline);
-            }
-        }
-
-        return pipelineNames;
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineSettingsReader.java b/src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineSettingsReader.java
deleted file mode 100644
index b793c7c..0000000
--- a/src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineSettingsReader.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to David Pilato (the "Author") under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. Author 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 fr.pilato.elasticsearch.tools.pipeline;
-
-import java.io.IOException;
-
-import fr.pilato.elasticsearch.tools.SettingsFinder.Defaults;
-import fr.pilato.elasticsearch.tools.SettingsReader;
-
-/**
- * Manage elasticsearch pipeline files.
- * 
- * @author hjk181
- */
-public class PipelineSettingsReader extends SettingsReader {
-
-    /**
-     * Read a pipeline
-     * @param root dir within the classpath
-     * @param pipeline the id of the pipeline (.json will be appended)
-     * @return The pipeline content
-     * @throws IOException if the connection with elasticsearch is failing
-     */
-    public static String readPipeline(String root, String pipeline) throws IOException {
-        if (root == null) {
-            return readPipeline(pipeline);
-        }
-        String settingsFile = root + "/" + Defaults.PipelineDir + "/" + pipeline + Defaults.JsonFileExtension;
-        return readFileFromClasspath(settingsFile);
-    }
-
-    /**
-     * Read a pipeline in default classpath dir
-     * @param pipeline the id of the pipeline (.json will be appended)
-     * @return The pipeline content
-     * @throws IOException if the connection with elasticsearch is failing
-     */
-    public static String readPipeline(String pipeline) throws IOException {
-        return readPipeline(Defaults.ConfigDir, pipeline);
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/template/TemplateFinder.java b/src/main/java/fr/pilato/elasticsearch/tools/template/TemplateFinder.java
deleted file mode 100644
index d404f63..0000000
--- a/src/main/java/fr/pilato/elasticsearch/tools/template/TemplateFinder.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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 fr.pilato.elasticsearch.tools.template;
-
-import fr.pilato.elasticsearch.tools.ResourceList;
-import fr.pilato.elasticsearch.tools.SettingsFinder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class TemplateFinder extends SettingsFinder {
-    private static final Logger logger = LoggerFactory.getLogger(TemplateFinder.class);
-
-    /**
-     * Find all templates in default classpath dir
-     * @return a list of templates
-     * @throws IOException if connection with elasticsearch is failing
-     * @throws URISyntaxException this should not happen
-     */
-    public static List<String> findTemplates() throws IOException, URISyntaxException {
-        return findTemplates(Defaults.ConfigDir);
-    }
-
-    /**
-     * Find all templates
-     * @param root dir within the classpath
-     * @return a list of templates
-     * @throws IOException if connection with elasticsearch is failing
-     * @throws URISyntaxException this should not happen
-     */
-    public static List<String> findTemplates(String root) throws IOException, URISyntaxException {
-        if (root == null) {
-            return findTemplates();
-        }
-
-        logger.debug("Looking for templates in classpath under [{}].", root);
-
-        final List<String> templateNames = new ArrayList<>();
-        String[] resources = ResourceList.getResources(root + "/" + Defaults.TemplateDir + "/"); // "es/_template/"
-        for (String resource : resources) {
-            if (!resource.isEmpty()) {
-                String withoutIndex = resource.substring(resource.indexOf("/")+1);
-                String template = withoutIndex.substring(0, withoutIndex.indexOf(Defaults.JsonFileExtension));
-                logger.trace(" - found [{}].", template);
-                templateNames.add(template);
-            }
-        }
-
-        return templateNames;
-    }
-}
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/template/TemplateSettingsReader.java b/src/main/java/fr/pilato/elasticsearch/tools/template/TemplateSettingsReader.java
deleted file mode 100644
index 8e7dbf4..0000000
--- a/src/main/java/fr/pilato/elasticsearch/tools/template/TemplateSettingsReader.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to David Pilato (the "Author") under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. Author 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 fr.pilato.elasticsearch.tools.template;
-
-import fr.pilato.elasticsearch.tools.SettingsFinder.Defaults;
-import fr.pilato.elasticsearch.tools.SettingsReader;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-/**
- * Manage elasticsearch template files
- * @author David Pilato
- */
-public class TemplateSettingsReader extends SettingsReader {
-
-	private static final Logger logger = LoggerFactory.getLogger(TemplateSettingsReader.class);
-
-	/**
-	 * Read a template
-	 * @param root dir within the classpath
-	 * @param template template name (.json will be appended)
-	 * @return The template content
-	 * @throws IOException if the connection with elasticsearch is failing
-	 */
-	public static String readTemplate(String root, String template) throws IOException {
-		if (root == null) {
-			return readTemplate(template);
-		}
-		String settingsFile = root + "/" + Defaults.TemplateDir + "/" + template + Defaults.JsonFileExtension;
-		return readFileFromClasspath(settingsFile);
-	}
-
-	/**
-	 * Read a template in default classpath dir
-	 * @param template template name (.json will be appended)
-	 * @return The template content
-	 * @throws IOException if the connection with elasticsearch is failing
-	 */
-	public static String readTemplate(String template) throws IOException {
-		return readTemplate(Defaults.ConfigDir, template);
-	}
-}
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/alias/AliasElasticsearchUpdater.java b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchAliasUpdater.java
similarity index 95%
rename from src/main/java/fr/pilato/elasticsearch/tools/alias/AliasElasticsearchUpdater.java
rename to src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchAliasUpdater.java
index 7929a79..aaa9192 100644
--- a/src/main/java/fr/pilato/elasticsearch/tools/alias/AliasElasticsearchUpdater.java
+++ b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchAliasUpdater.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package fr.pilato.elasticsearch.tools.alias;
+package fr.pilato.elasticsearch.tools.updaters;
 
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.client.Client;
@@ -27,9 +27,9 @@ import org.elasticsearch.client.RestClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class AliasElasticsearchUpdater {
+public class ElasticsearchAliasUpdater {
 
-    private static final Logger logger = LoggerFactory.getLogger(AliasElasticsearchUpdater.class);
+    private static final Logger logger = LoggerFactory.getLogger(ElasticsearchAliasUpdater.class);
 
     /**
      * Create an alias if needed
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchComponentTemplateUpdater.java b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchComponentTemplateUpdater.java
new file mode 100644
index 0000000..81dccc7
--- /dev/null
+++ b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchComponentTemplateUpdater.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to David Pilato (the "Author") under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Author 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 fr.pilato.elasticsearch.tools.updaters;
+
+import fr.pilato.elasticsearch.tools.util.SettingsFinder;
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import static fr.pilato.elasticsearch.tools.util.SettingsReader.getJsonContent;
+
+/**
+ * Manage elasticsearch component templates
+ * @author David Pilato
+ */
+public class ElasticsearchComponentTemplateUpdater {
+
+	private static final Logger logger = LoggerFactory.getLogger(ElasticsearchComponentTemplateUpdater.class);
+
+	/**
+	 * Create an component template in Elasticsearch.
+	 * @param client Elasticsearch client
+	 * @param root dir within the classpath
+	 * @param template Template name
+	 * @param force set it to true if you want to force cleaning template before adding it
+	 * @throws Exception if something goes wrong
+	 */
+	public static void createComponentTemplate(RestClient client, String root, String template, boolean force) throws Exception {
+		String json = getJsonContent(root, SettingsFinder.Defaults.ComponentTemplatesDir, template);
+		createComponentTemplateWithJson(client, template, json, force);
+	}
+
+	/**
+	 * Create a new component template in Elasticsearch
+	 * @param client Elasticsearch client
+	 * @param template Template name
+	 * @param json JSon content for the template
+	 * @param force set it to true if you want to force cleaning template before adding it
+	 * @throws Exception if something goes wrong
+	 */
+	public static void createComponentTemplateWithJson(RestClient client, String template, String json, boolean force) throws Exception {
+		if (isComponentTemplateExist(client, template)) {
+			if (force) {
+				logger.debug("Component Template [{}] already exists. Force is set. Removing it.", template);
+				removeComponentTemplate(client, template);
+			} else {
+				logger.debug("Component Template [{}] already exists.", template);
+			}
+		}
+
+		if (!isComponentTemplateExist(client, template)) {
+			logger.debug("Component Template [{}] doesn't exist. Creating it.", template);
+			createComponentTemplateWithJsonInElasticsearch(client, template, json);
+		}
+	}
+
+	/**
+	 * Create a new component template in Elasticsearch
+	 * @param client Elasticsearch client
+	 * @param template Template name
+	 * @param json JSon content for the template
+	 * @throws Exception if something goes wrong
+	 */
+	private static void createComponentTemplateWithJsonInElasticsearch(RestClient client, String template, String json) throws Exception {
+		logger.trace("createComponentTemplate([{}])", template);
+
+		assert client != null;
+		assert template != null;
+
+		Request request = new Request("PUT", "/_component_template/" + template);
+		request.setJsonEntity(json);
+		Response response = client.performRequest(request);
+
+		if (response.getStatusLine().getStatusCode() != 200) {
+			logger.warn("Could not create component template [{}]", template);
+			throw new Exception("Could not create component template ["+template+"].");
+		}
+
+		logger.trace("/createComponentTemplate([{}])", template);
+	}
+
+	/**
+	 * Check if an component template exists
+	 * @param client Elasticsearch client
+	 * @param template template name
+	 * @return true if the template exists
+	 * @throws IOException if something goes wrong
+	 */
+	public static boolean isComponentTemplateExist(RestClient client, String template) throws IOException {
+		Response response = client.performRequest(new Request("HEAD", "/_component_template/" + template));
+		return response.getStatusLine().getStatusCode() == 200;
+	}
+
+	/**
+	 * Remove an component template
+	 * @param client Elasticsearch client
+	 * @param template template name
+	 * @throws Exception if something goes wrong
+	 */
+	public static void removeComponentTemplate(RestClient client, String template) throws Exception {
+		logger.trace("removeComponentTemplate({})", template);
+		client.performRequest(new Request("DELETE", "/_component_template/" + template));
+		logger.trace("/removeComponentTemplate({})", template);
+	}
+}
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchIndexTemplateUpdater.java b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchIndexTemplateUpdater.java
new file mode 100644
index 0000000..ab10d6e
--- /dev/null
+++ b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchIndexTemplateUpdater.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to David Pilato (the "Author") under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Author 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 fr.pilato.elasticsearch.tools.updaters;
+
+import fr.pilato.elasticsearch.tools.util.SettingsFinder;
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import static fr.pilato.elasticsearch.tools.util.SettingsReader.getJsonContent;
+
+/**
+ * Manage elasticsearch index templates
+ * @author David Pilato
+ */
+public class ElasticsearchIndexTemplateUpdater {
+
+	private static final Logger logger = LoggerFactory.getLogger(ElasticsearchIndexTemplateUpdater.class);
+
+	/**
+	 * Create an index template in Elasticsearch.
+	 * @param client Elasticsearch client
+	 * @param root dir within the classpath
+	 * @param template Template name
+	 * @param force set it to true if you want to force cleaning template before adding it
+	 * @throws Exception if something goes wrong
+	 */
+	public static void createIndexTemplate(RestClient client, String root, String template, boolean force) throws Exception {
+		String json = getJsonContent(root, SettingsFinder.Defaults.IndexTemplatesDir, template);
+		createIndexTemplateWithJson(client, template, json, force);
+	}
+
+	/**
+	 * Create a new index template in Elasticsearch
+	 * @param client Elasticsearch client
+	 * @param template Template name
+	 * @param json JSon content for the template
+	 * @param force set it to true if you want to force cleaning template before adding it
+	 * @throws Exception if something goes wrong
+	 */
+	public static void createIndexTemplateWithJson(RestClient client, String template, String json, boolean force) throws Exception {
+		if (isIndexTemplateExist(client, template)) {
+			if (force) {
+				logger.debug("Index Template [{}] already exists. Force is set. Removing it.", template);
+				removeIndexTemplate(client, template);
+			} else {
+				logger.debug("Index Template [{}] already exists.", template);
+			}
+		}
+
+		if (!isIndexTemplateExist(client, template)) {
+			logger.debug("Index Template [{}] doesn't exist. Creating it.", template);
+			createIndexTemplateWithJsonInElasticsearch(client, template, json);
+		}
+	}
+
+	/**
+	 * Create a new index template in Elasticsearch
+	 * @param client Elasticsearch client
+	 * @param template Template name
+	 * @param json JSon content for the template
+	 * @throws Exception if something goes wrong
+	 */
+	private static void createIndexTemplateWithJsonInElasticsearch(RestClient client, String template, String json) throws Exception {
+		logger.trace("createIndexTemplate([{}])", template);
+
+		assert client != null;
+		assert template != null;
+
+		Request request = new Request("PUT", "/_index_template/" + template);
+		request.setJsonEntity(json);
+		Response response = client.performRequest(request);
+
+		if (response.getStatusLine().getStatusCode() != 200) {
+			logger.warn("Could not create index template [{}]", template);
+			throw new Exception("Could not create index template ["+template+"].");
+		}
+
+		logger.trace("/createIndexTemplate([{}])", template);
+	}
+
+	/**
+	 * Check if an index template exists
+	 * @param client Elasticsearch client
+	 * @param template template name
+	 * @return true if the template exists
+	 * @throws IOException if something goes wrong
+	 */
+	public static boolean isIndexTemplateExist(RestClient client, String template) throws IOException {
+		Response response = client.performRequest(new Request("HEAD", "/_index_template/" + template));
+		return response.getStatusLine().getStatusCode() == 200;
+	}
+
+	/**
+	 * Remove an index template
+	 * @param client Elasticsearch client
+	 * @param template template name
+	 * @throws Exception if something goes wrong
+	 */
+	public static void removeIndexTemplate(RestClient client, String template) throws Exception {
+		logger.trace("removeIndexTemplate({})", template);
+		client.performRequest(new Request("DELETE", "/_index_template/" + template));
+		logger.trace("/removeIndexTemplate({})", template);
+	}
+}
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/index/IndexElasticsearchUpdater.java b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchIndexUpdater.java
similarity index 72%
rename from src/main/java/fr/pilato/elasticsearch/tools/index/IndexElasticsearchUpdater.java
rename to src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchIndexUpdater.java
index 8ff368b..07ade05 100644
--- a/src/main/java/fr/pilato/elasticsearch/tools/index/IndexElasticsearchUpdater.java
+++ b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchIndexUpdater.java
@@ -17,26 +17,33 @@
  * under the License.
  */
 
-package fr.pilato.elasticsearch.tools.index;
+package fr.pilato.elasticsearch.tools.updaters;
 
+import fr.pilato.elasticsearch.tools.util.SettingsFinder;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
+import org.elasticsearch.client.ResponseException;
 import org.elasticsearch.client.RestClient;
 import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.index.IndexNotFoundException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+
+import static fr.pilato.elasticsearch.tools.util.SettingsReader.getJsonContent;
+
 /**
  * Manage elasticsearch index settings
  * @author David Pilato
  */
-public class IndexElasticsearchUpdater {
+public class ElasticsearchIndexUpdater {
 
-	private static final Logger logger = LoggerFactory.getLogger(IndexElasticsearchUpdater.class);
+	private static final Logger logger = LoggerFactory.getLogger(ElasticsearchIndexUpdater.class);
 
 	/**
 	 * Create a new index in Elasticsearch. Read also _settings.json if exists.
@@ -48,21 +55,8 @@ public class IndexElasticsearchUpdater {
 	 */
 	@Deprecated
 	public static void createIndex(Client client, String root, String index, boolean force) throws Exception {
-		String settings = IndexSettingsReader.readSettings(root, index);
-		createIndexWithSettings(client, index, settings, force);
-	}
-
-	/**
-	 * Create a new index in Elasticsearch. Read also _settings.json if exists in default classpath dir.
-	 * @param client Elasticsearch client
-	 * @param index Index name
-	 * @param force Remove index if exists (Warning: remove all data)
-	 * @throws Exception if the elasticsearch API call is failing
-	 */
-	@Deprecated
-	public static void createIndex(Client client, String index, boolean force) throws Exception {
-		String settings = IndexSettingsReader.readSettings(index);
-		createIndexWithSettings(client, index, settings, force);
+		String json = getJsonContent(root, index, SettingsFinder.Defaults.IndexSettingsFileName);
+		createIndexWithSettings(client, index, json, force);
 	}
 
 	/**
@@ -94,16 +88,20 @@ public class IndexElasticsearchUpdater {
 	 * @throws Exception if the elasticsearch API call is failing
 	 */
 	@Deprecated
-	private static void removeIndexInElasticsearch(Client client, String index) throws Exception {
+	public static void removeIndexInElasticsearch(Client client, String index) throws Exception {
 		logger.trace("removeIndex([{}])", index);
 
 		assert client != null;
 		assert index != null;
 
-        AcknowledgedResponse response = client.admin().indices().prepareDelete(index).get();
-		if (!response.isAcknowledged()) {
-			logger.warn("Could not delete index [{}]", index);
-			throw new Exception("Could not delete index ["+index+"].");
+		try {
+			AcknowledgedResponse response = client.admin().indices().prepareDelete(index).get();
+			if (!response.isAcknowledged()) {
+				logger.warn("Could not delete index [{}]", index);
+				throw new Exception("Could not delete index ["+index+"].");
+			}
+		} catch (IndexNotFoundException e) {
+			// This is expected
 		}
 
 		logger.trace("/removeIndex([{}])", index);
@@ -145,10 +143,9 @@ public class IndexElasticsearchUpdater {
 	 * @param client Elasticsearch client
 	 * @param index Index name
 	 * @param settings Settings if any, null if no update settings
-	 * @throws Exception if the elasticsearch API call is failing
 	 */
 	@Deprecated
-	private static void updateIndexWithSettingsInElasticsearch(Client client, String index, String settings) throws Exception {
+	private static void updateIndexWithSettingsInElasticsearch(Client client, String index, String settings) {
 		logger.trace("updateIndex([{}])", index);
 
 		assert client != null;
@@ -201,27 +198,13 @@ public class IndexElasticsearchUpdater {
 	 * @param client Elasticsearch client
 	 * @param root dir within the classpath
 	 * @param index Index name
-	 * @throws Exception if the elasticsearch API call is failing
-	 */
-	@Deprecated
-	public static void updateSettings(Client client, String root, String index) throws Exception {
-		String settings = IndexSettingsReader.readUpdateSettings(root, index);
-		updateIndexWithSettingsInElasticsearch(client, index, settings);
-	}
-
-	/**
-	 * Update index settings in Elasticsearch. Read also _update_settings.json if exists in default classpath dir.
-	 * @param client Elasticsearch client
-	 * @param index Index name
-	 * @throws Exception if the elasticsearch API call is failing
 	 */
 	@Deprecated
-	public static void updateSettings(Client client, String index) throws Exception {
-		String settings = IndexSettingsReader.readUpdateSettings(index);
-		updateIndexWithSettingsInElasticsearch(client, index, settings);
+	public static void updateSettings(Client client, String root, String index) throws IOException {
+		String json = getJsonContent(root, index, SettingsFinder.Defaults.UpdateIndexSettingsFileName);
+		updateIndexWithSettingsInElasticsearch(client, index, json);
 	}
 
-
 	/**
 	 * Update index mapping in Elasticsearch. Read also _update_mapping.json if exists.
 	 * @param client Elasticsearch client
@@ -229,20 +212,9 @@ public class IndexElasticsearchUpdater {
 	 * @param index Index name
 	 */
 	@Deprecated
-	public static void updateMapping(Client client, String root, String index) {
-		String mapping = IndexSettingsReader.readUpdateMapping(root, index);
-		updateMappingInElasticsearch(client, index, mapping);
-	}
-
-	/**
-	 * Update index mapping in Elasticsearch. Read also _update_mapping.json if exists in default classpath dir.
-	 * @param client Elasticsearch client
-	 * @param index Index name
-	 */
-	@Deprecated
-	public static void updateMapping(Client client, String index) {
-		String mapping = IndexSettingsReader.readUpdateMapping(index);
-		updateMappingInElasticsearch(client, index, mapping);
+	public static void updateMapping(Client client, String root, String index) throws IOException {
+		String json = getJsonContent(root, index, SettingsFinder.Defaults.UpdateIndexMappingFileName);
+		updateMappingInElasticsearch(client, index, json);
 	}
 
 	/**
@@ -254,20 +226,8 @@ public class IndexElasticsearchUpdater {
 	 * @throws Exception if the elasticsearch API call is failing
 	 */
 	public static void createIndex(RestClient client, String root, String index, boolean force) throws Exception {
-		String settings = IndexSettingsReader.readSettings(root, index);
-		createIndexWithSettings(client, index, settings, force);
-	}
-
-	/**
-	 * Create a new index in Elasticsearch. Read also _settings.json if exists in default classpath dir.
-	 * @param client Elasticsearch client
-	 * @param index Index name
-	 * @param force Remove index if exists (Warning: remove all data)
-	 * @throws Exception if the elasticsearch API call is failing
-	 */
-	public static void createIndex(RestClient client, String index, boolean force) throws Exception {
-		String settings = IndexSettingsReader.readSettings(index);
-		createIndexWithSettings(client, index, settings, force);
+		String json = getJsonContent(root, index, SettingsFinder.Defaults.IndexSettingsFileName);
+		createIndexWithSettings(client, index, json, force);
 	}
 
 	/**
@@ -297,14 +257,21 @@ public class IndexElasticsearchUpdater {
 	 * @param index Index name
 	 * @throws Exception if the elasticsearch API call is failing
 	 */
-	private static void removeIndexInElasticsearch(RestClient client, String index) throws Exception {
+	public static void removeIndexInElasticsearch(RestClient client, String index) throws Exception {
 		logger.trace("removeIndex([{}])", index);
 
 		assert client != null;
 		assert index != null;
 
-		Response response = client.performRequest(new Request("DELETE", "/" + index));
-		if (response.getStatusLine().getStatusCode() != 200) {
+		int statusCode;
+
+		try {
+			Response response = client.performRequest(new Request("DELETE", "/" + index));
+			statusCode = response.getStatusLine().getStatusCode();
+		} catch (ResponseException e) {
+			statusCode = e.getResponse().getStatusLine().getStatusCode();
+		}
+		if (statusCode != 200 && statusCode != 404) {
 			logger.warn("Could not delete index [{}]", index);
 			throw new Exception("Could not delete index ["+index+"].");
 		}
@@ -412,19 +379,8 @@ public class IndexElasticsearchUpdater {
 	 * @throws Exception if the elasticsearch API call is failing
 	 */
 	public static void updateSettings(RestClient client, String root, String index) throws Exception {
-		String settings = IndexSettingsReader.readUpdateSettings(root, index);
-		updateIndexWithSettingsInElasticsearch(client, index, settings);
-	}
-
-	/**
-	 * Update index settings in Elasticsearch. Read also _update_settings.json if exists in default classpath dir.
-	 * @param client Elasticsearch client
-	 * @param index Index name
-	 * @throws Exception if the elasticsearch API call is failing
-	 */
-	public static void updateSettings(RestClient client, String index) throws Exception {
-		String settings = IndexSettingsReader.readUpdateSettings(index);
-		updateIndexWithSettingsInElasticsearch(client, index, settings);
+		String json = getJsonContent(root, index, SettingsFinder.Defaults.UpdateIndexSettingsFileName);
+		updateIndexWithSettingsInElasticsearch(client, index, json);
 	}
 
 	/**
@@ -435,18 +391,7 @@ public class IndexElasticsearchUpdater {
 	 * @throws Exception if the elasticsearch API call is failing
 	 */
 	public static void updateMapping(RestClient client, String root, String index) throws Exception {
-		String mapping = IndexSettingsReader.readUpdateMapping(root, index);
-		updateMappingInElasticsearch(client, index, mapping);
-	}
-
-	/**
-	 * Update index mapping in Elasticsearch. Read also _update_mapping.json if exists in default classpath dir.
-	 * @param client Elasticsearch client
-	 * @param index Index name
-	 * @throws Exception if the elasticsearch API call is failing
-	 */
-	public static void updateMapping(RestClient client, String index) throws Exception {
-		String mapping = IndexSettingsReader.readUpdateMapping(index);
-		updateMappingInElasticsearch(client, index, mapping);
+		String json = getJsonContent(root, index, SettingsFinder.Defaults.UpdateIndexMappingFileName);
+		updateMappingInElasticsearch(client, index, json);
 	}
 }
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineElasticsearchUpdater.java b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchPipelineUpdater.java
similarity index 85%
rename from src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineElasticsearchUpdater.java
rename to src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchPipelineUpdater.java
index 4d93c55..a86cf00 100644
--- a/src/main/java/fr/pilato/elasticsearch/tools/pipeline/PipelineElasticsearchUpdater.java
+++ b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchPipelineUpdater.java
@@ -17,10 +17,11 @@
  * under the License.
  */
 
-package fr.pilato.elasticsearch.tools.pipeline;
+package fr.pilato.elasticsearch.tools.updaters;
 
 import java.io.IOException;
 
+import fr.pilato.elasticsearch.tools.util.SettingsFinder;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.client.ResponseException;
@@ -28,15 +29,17 @@ import org.elasticsearch.client.RestClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static fr.pilato.elasticsearch.tools.util.SettingsReader.getJsonContent;
+
 /**
  * Handles ingest pipeline creation.
  * 
  * @author hjk181
  *
  */
-public class PipelineElasticsearchUpdater {
+public class ElasticsearchPipelineUpdater {
 
-    private static final Logger logger = LoggerFactory.getLogger(PipelineElasticsearchUpdater.class);
+    private static final Logger logger = LoggerFactory.getLogger(ElasticsearchPipelineUpdater.class);
 
     /**
      * Create a pipeline in Elasticsearch.
@@ -48,20 +51,7 @@ public class PipelineElasticsearchUpdater {
      * @throws Exception if something goes wrong
      */
     public static void createPipeline(RestClient client, String root, String pipeline, boolean force) throws Exception {
-        String json = PipelineSettingsReader.readPipeline(root, pipeline);
-        createPipelineWithJson(client, pipeline, json, force);
-    }
-
-    /**
-     * Create a pipeline in Elasticsearch. Read content from default classpath dir.
-     * 
-     * @param client Elasticsearch client
-     * @param pipeline the id of the pipeline
-     * @param force set it to true if you want to force cleaning pipeline before adding it
-     * @throws Exception if something goes wrong
-     */
-    public static void createPipeline(RestClient client, String pipeline, boolean force) throws Exception {
-        String json = PipelineSettingsReader.readPipeline(pipeline);
+        String json = getJsonContent(root, SettingsFinder.Defaults.PipelineDir, pipeline);
         createPipelineWithJson(client, pipeline, json, force);
     }
 
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/template/TemplateElasticsearchUpdater.java b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchTemplateUpdater.java
similarity index 81%
rename from src/main/java/fr/pilato/elasticsearch/tools/template/TemplateElasticsearchUpdater.java
rename to src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchTemplateUpdater.java
index 65f1180..243678b 100644
--- a/src/main/java/fr/pilato/elasticsearch/tools/template/TemplateElasticsearchUpdater.java
+++ b/src/main/java/fr/pilato/elasticsearch/tools/updaters/ElasticsearchTemplateUpdater.java
@@ -17,8 +17,9 @@
  * under the License.
  */
 
-package fr.pilato.elasticsearch.tools.template;
+package fr.pilato.elasticsearch.tools.updaters;
 
+import fr.pilato.elasticsearch.tools.util.SettingsFinder;
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.Request;
@@ -30,13 +31,16 @@ import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 
+import static fr.pilato.elasticsearch.tools.util.SettingsReader.getJsonContent;
+
 /**
  * Manage elasticsearch templates
  * @author David Pilato
  */
-public class TemplateElasticsearchUpdater {
+@Deprecated
+public class ElasticsearchTemplateUpdater {
 
-	private static final Logger logger = LoggerFactory.getLogger(TemplateElasticsearchUpdater.class);
+	private static final Logger logger = LoggerFactory.getLogger(ElasticsearchTemplateUpdater.class);
 
 	/**
 	 * Create a template in Elasticsearch.
@@ -49,21 +53,7 @@ public class TemplateElasticsearchUpdater {
 	 */
 	@Deprecated
 	public static void createTemplate(Client client, String root, String template, boolean force) throws Exception {
-		String json = TemplateSettingsReader.readTemplate(root, template);
-		createTemplateWithJson(client, template, json, force);
-	}
-
-	/**
-	 * Create a template in Elasticsearch. Read read content from default classpath dir.
-	 * @param client Elasticsearch client
-	 * @param template Template name
-     * @param force set it to true if you want to force cleaning template before adding it
-     * @throws Exception if something goes wrong
-     * @deprecated Will be removed when we don't support TransportClient anymore
-	 */
-	@Deprecated
-	public static void createTemplate(Client client, String template, boolean force) throws Exception {
-		String json = TemplateSettingsReader.readTemplate(template);
+		String json = getJsonContent(root, SettingsFinder.Defaults.TemplateDir, template);
 		createTemplateWithJson(client, template, json, force);
 	}
 
@@ -147,38 +137,28 @@ public class TemplateElasticsearchUpdater {
 	}
 
 	/**
-	 * Create a template in Elasticsearch.
+	 * Create a legacy template in Elasticsearch.
 	 * @param client Elasticsearch client
 	 * @param root dir within the classpath
 	 * @param template Template name
      * @param force set it to true if you want to force cleaning template before adding it
      * @throws Exception if something goes wrong
 	 */
+	@Deprecated
 	public static void createTemplate(RestClient client, String root, String template, boolean force) throws Exception {
-		String json = TemplateSettingsReader.readTemplate(root, template);
+		String json = getJsonContent(root, SettingsFinder.Defaults.TemplateDir, template);
 		createTemplateWithJson(client, template, json, force);
 	}
 
 	/**
-	 * Create a template in Elasticsearch. Read read content from default classpath dir.
-	 * @param client Elasticsearch client
-	 * @param template Template name
-     * @param force set it to true if you want to force cleaning template before adding it
-     * @throws Exception if something goes wrong
-	 */
-	public static void createTemplate(RestClient client, String template, boolean force) throws Exception {
-		String json = TemplateSettingsReader.readTemplate(template);
-		createTemplateWithJson(client, template, json, force);
-	}
-
-	/**
-	 * Create a new template in Elasticsearch
+	 * Create a new legacy template in Elasticsearch
 	 * @param client Elasticsearch client
 	 * @param template Template name
 	 * @param json JSon content for the template
 	 * @param force set it to true if you want to force cleaning template before adding it
      * @throws Exception if something goes wrong
 	 */
+	@Deprecated
 	public static void createTemplateWithJson(RestClient client, String template, String json, boolean force) throws Exception {
 		if (isTemplateExist(client, template)) {
 			if (force) {
@@ -196,12 +176,13 @@ public class TemplateElasticsearchUpdater {
 	}
 
 	/**
-	 * Create a new index in Elasticsearch
+	 * Create a new legacy template in Elasticsearch
 	 * @param client Elasticsearch client
 	 * @param template Template name
 	 * @param json JSon content for the template
      * @throws Exception if something goes wrong
 	 */
+	@Deprecated
 	private static void createTemplateWithJsonInElasticsearch(RestClient client, String template, String json) throws Exception {
 		logger.trace("createTemplate([{}])", template);
 
@@ -221,23 +202,25 @@ public class TemplateElasticsearchUpdater {
 	}
 
 	/**
-	 * Check if a template exists
+	 * Check if a legacy template exists
      * @param client Elasticsearch client
 	 * @param template template name
      * @return true if the template exists
      * @throws IOException if something goes wrong
 	 */
+	@Deprecated
 	public static boolean isTemplateExist(RestClient client, String template) throws IOException {
 		Response response = client.performRequest(new Request("HEAD", "/_template/" + template));
 		return response.getStatusLine().getStatusCode() == 200;
 	}
 
 	/**
-	 * Remove a template
+	 * Remove a legacy template
      * @param client Elasticsearch client
 	 * @param template template name
      * @throws Exception if something goes wrong
 	 */
+	@Deprecated
 	public static void removeTemplate(RestClient client, String template) throws Exception {
 		logger.trace("removeTemplate({})", template);
 		client.performRequest(new Request("DELETE", "/_template/" + template));
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/ResourceList.java b/src/main/java/fr/pilato/elasticsearch/tools/util/ResourceList.java
similarity index 59%
rename from src/main/java/fr/pilato/elasticsearch/tools/ResourceList.java
rename to src/main/java/fr/pilato/elasticsearch/tools/util/ResourceList.java
index 633d46b..ff7fe3f 100644
--- a/src/main/java/fr/pilato/elasticsearch/tools/ResourceList.java
+++ b/src/main/java/fr/pilato/elasticsearch/tools/util/ResourceList.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package fr.pilato.elasticsearch.tools;
+package fr.pilato.elasticsearch.tools.util;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,9 +28,11 @@ import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
@@ -113,4 +115,83 @@ public class ResourceList {
         logger.trace("did not find any resource. returning empty array");
         return NO_RESOURCE;
     }
+
+    /**
+     * Extract the list of available names from a given array of JSON resources
+     * @param resources available resource files
+     * @return A list of names to use
+     */
+    public static List<String> extractNamesFromJsonResources(String[] resources) {
+        final List<String> names = new ArrayList<>();
+        for (String resource : resources) {
+            if (!resource.isEmpty()) {
+                String withoutIndex = resource.substring(resource.indexOf("/")+1);
+                String name = withoutIndex.substring(0, withoutIndex.indexOf(SettingsFinder.Defaults.JsonFileExtension));
+                logger.trace(" - found [{}].", name);
+                names.add(name);
+            }
+        }
+
+        return names;
+    }
+
+    /**
+     * Get the list of resource names (without the .json extension) from a given root and folder
+     * @param root Root dir to scan from. Like "/es".
+     * @param subdir Subdir name like "_index_templates".
+     * @return A list of names we found
+     * @throws URISyntaxException When a file:// resource can not be converted to URL
+     * @throws IOException When a URL can not be decoded
+     */
+    public static List<String> getResourceNames(final String root, final String subdir) throws URISyntaxException, IOException {
+        String path = root;
+        if (root == null) {
+            path = SettingsFinder.Defaults.ConfigDir;
+        }
+        path += "/" + subdir + "/";
+        logger.debug("Looking for resources in classpath under [{}].", path);
+        return extractNamesFromJsonResources(ResourceList.getResources(path));
+    }
+
+    /**
+     * Find all indices existing in a given classpath dir
+     * @param root dir within the classpath
+     * @return a list of indices
+     * @throws IOException if connection with elasticsearch is failing
+     * @throws URISyntaxException this should not happen
+     */
+    public static List<String> findIndexNames(final String root) throws IOException, URISyntaxException {
+        String path = root;
+        if (root == null) {
+            path = SettingsFinder.Defaults.ConfigDir;
+        }
+
+        logger.debug("Looking for indices in classpath under [{}].", path);
+
+        final List<String> indexNames = new ArrayList<>();
+        final Set<String> keys = new HashSet<>();
+        String[] resources = ResourceList.getResources(path + "/"); // "es/" or "a/b/c/"
+        for (String resource : resources) {
+            if (!resource.isEmpty()) {
+                logger.trace(" - resource [{}].", resource);
+                String key;
+                if (resource.contains("/")) {
+                    key = resource.substring(0, resource.indexOf("/"));
+                } else {
+                    key = resource;
+                }
+                if (!key.equals(SettingsFinder.Defaults.IndexTemplatesDir) &&
+                        !key.equals(SettingsFinder.Defaults.ComponentTemplatesDir) &&
+                        !key.equals(SettingsFinder.Defaults.TemplateDir) &&
+                        !key.equals(SettingsFinder.Defaults.PipelineDir) &&
+                        !keys.contains(key)) {
+                    logger.trace(" - found [{}].", key);
+                    keys.add(key);
+                    indexNames.add(key);
+                }
+            }
+        }
+
+        return indexNames;
+    }
 }
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/SettingsFinder.java b/src/main/java/fr/pilato/elasticsearch/tools/util/SettingsFinder.java
similarity index 85%
rename from src/main/java/fr/pilato/elasticsearch/tools/SettingsFinder.java
rename to src/main/java/fr/pilato/elasticsearch/tools/util/SettingsFinder.java
index 2a7da2b..c4d9ecd 100644
--- a/src/main/java/fr/pilato/elasticsearch/tools/SettingsFinder.java
+++ b/src/main/java/fr/pilato/elasticsearch/tools/util/SettingsFinder.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package fr.pilato.elasticsearch.tools;
+package fr.pilato.elasticsearch.tools.util;
 
 public class SettingsFinder {
 
@@ -28,10 +28,12 @@ public class SettingsFinder {
 		public static String ConfigDir = "elasticsearch";
 
 		public static String JsonFileExtension = ".json";
-		public static String IndexSettingsFileName = "_settings.json";
-		public static String UpdateIndexSettingsFileName = "_update_settings.json";
-		public static String UpdateIndexMappingFileName = "_update_mapping.json";
+		public static String IndexSettingsFileName = "_settings";
+		public static String UpdateIndexSettingsFileName = "_update_settings";
+		public static String UpdateIndexMappingFileName = "_update_mapping";
 		public static String TemplateDir = "_template";
+		public static String IndexTemplatesDir = "_index_templates";
+		public static String ComponentTemplatesDir = "_component_templates";
 		public static String PipelineDir = "_pipeline";
 
 		/**
diff --git a/src/main/java/fr/pilato/elasticsearch/tools/SettingsReader.java b/src/main/java/fr/pilato/elasticsearch/tools/util/SettingsReader.java
similarity index 80%
rename from src/main/java/fr/pilato/elasticsearch/tools/SettingsReader.java
rename to src/main/java/fr/pilato/elasticsearch/tools/util/SettingsReader.java
index 4a82c89..d808637 100644
--- a/src/main/java/fr/pilato/elasticsearch/tools/SettingsReader.java
+++ b/src/main/java/fr/pilato/elasticsearch/tools/util/SettingsReader.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package fr.pilato.elasticsearch.tools;
+package fr.pilato.elasticsearch.tools.util;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.text.StringSubstitutor;
@@ -53,4 +53,13 @@ public class SettingsReader {
 		return StringSubstitutor.replace(content, System.getenv());
 	}
 
+	public static String getJsonContent(String root, String subdir, String name) throws IOException {
+		String path = root;
+		if (root == null) {
+			path = SettingsFinder.Defaults.ConfigDir;
+		}
+		path += "/" + subdir + "/" + name + SettingsFinder.Defaults.JsonFileExtension;
+		logger.debug("Reading file [{}] from the classpath.", path);
+		return readFileFromClasspath(path);
+	}
 }


Test patch:
diff --git a/src/test/java/fr/pilato/elasticsearch/tools/AbstractBeyonderTest.java b/src/test/java/fr/pilato/elasticsearch/tools/AbstractBeyonderTest.java
index 1d97a52..d180caa 100644
--- a/src/test/java/fr/pilato/elasticsearch/tools/AbstractBeyonderTest.java
+++ b/src/test/java/fr/pilato/elasticsearch/tools/AbstractBeyonderTest.java
@@ -36,6 +36,7 @@ import java.io.IOException;
 import java.net.ConnectException;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 import static java.util.Collections.singletonList;
 import static org.hamcrest.Matchers.containsString;
@@ -56,7 +57,9 @@ public abstract class AbstractBeyonderTest {
 
     abstract protected void testBeyonder(String root,
                                          List<String> indices,
-                                         List<String> templates) throws Exception;
+                                         List<String> templates,
+                                         List<String> componentTemplates,
+                                         List<String> indexTemplates) throws Exception;
 
     private static RestClient client;
 
@@ -101,12 +104,26 @@ public abstract class AbstractBeyonderTest {
         }
     }
 
+    @FunctionalInterface
+    public interface ThrowingConsumer<E extends Exception> {
+        void run() throws E;
+    }
+
+    protected void launchAndIgnoreFailure(ThrowingConsumer<Exception> code) {
+        try {
+            code.run();
+        } catch (Exception e) {
+            logger.debug("Got an error while calling the cleanup method: {}", e.getMessage());
+            logger.trace("StackTrace:", e);
+        }
+    }
+
     @Test
     public void testDefaultDir() throws Exception {
         // Default dir es
         testBeyonder(null,
                 singletonList("twitter"),
-                null);
+                null, null, null);
     }
 
     @Test
@@ -114,7 +131,7 @@ public abstract class AbstractBeyonderTest {
         // Single index/single type
         testBeyonder("models/oneindexonetype",
                 singletonList("twitter"),
-                null);
+                null, null, null);
     }
 
     @Test
@@ -122,7 +139,7 @@ public abstract class AbstractBeyonderTest {
         // Custom settings (analyzer)
         testBeyonder("models/settingsanalyzer",
                 singletonList("twitter"),
-                null);
+                null, null, null);
     }
 
     @Test
@@ -130,7 +147,7 @@ public abstract class AbstractBeyonderTest {
         // 1 index and no type
         testBeyonder("models/oneindexnotype",
                 singletonList("twitter"),
-                null);
+                null, null, null);
     }
 
     @Test
@@ -138,13 +155,12 @@ public abstract class AbstractBeyonderTest {
         // 1 template
         testBeyonder("models/template",
                 null,
-                singletonList("twitter_template"));
+                singletonList("twitter_template"), null, null);
     }
 
     @Test
     public void testWrongClasspathDir() throws Exception {
         testBeyonder("models/bad-classpath-7/doesnotexist",
-                null,
-                null);
+                null, null, null, null);
     }
 }
diff --git a/src/test/java/fr/pilato/elasticsearch/tools/BeyonderRestIT.java b/src/test/java/fr/pilato/elasticsearch/tools/BeyonderRestIT.java
index b9a36b0..65a4da5 100644
--- a/src/test/java/fr/pilato/elasticsearch/tools/BeyonderRestIT.java
+++ b/src/test/java/fr/pilato/elasticsearch/tools/BeyonderRestIT.java
@@ -19,15 +19,17 @@
 
 package fr.pilato.elasticsearch.tools;
 
-import fr.pilato.elasticsearch.tools.alias.AliasElasticsearchUpdater;
-import fr.pilato.elasticsearch.tools.index.IndexElasticsearchUpdater;
-import fr.pilato.elasticsearch.tools.pipeline.PipelineElasticsearchUpdater;
+import fr.pilato.elasticsearch.tools.updaters.ElasticsearchAliasUpdater;
+import fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexUpdater;
+import fr.pilato.elasticsearch.tools.updaters.ElasticsearchPipelineUpdater;
+import fr.pilato.elasticsearch.tools.util.SettingsFinder;
 import org.apache.commons.beanutils.BeanUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpEntity;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.client.RestClient;
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -41,8 +43,11 @@ import java.util.List;
 import java.util.Map;
 
 import static fr.pilato.elasticsearch.tools.JsonUtil.asMap;
-import static fr.pilato.elasticsearch.tools.index.IndexElasticsearchUpdater.isIndexExist;
-import static fr.pilato.elasticsearch.tools.template.TemplateElasticsearchUpdater.isTemplateExist;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchComponentTemplateUpdater.isComponentTemplateExist;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexTemplateUpdater.isIndexTemplateExist;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexUpdater.isIndexExist;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchTemplateUpdater.isTemplateExist;
+import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
@@ -70,13 +75,26 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
         }
     }
 
-    @Before
-    public void cleanCluster() {
-        try {
-            client.performRequest(new Request("DELETE", "/_all"));
-        } catch (IOException e) {
-            assumeNoException(e);
-        }
+    @Before @After
+    public void cleanCluster() throws Exception {
+        // DELETE /twitter
+        launchAndIgnoreFailure(() -> client.performRequest(new Request("DELETE", "/twitter")));
+        // DELETE /test_aliases
+        launchAndIgnoreFailure(() -> client.performRequest(new Request("DELETE", "/test_aliases")));
+
+        // DELETE /_ingest/pipeline/twitter_pipeline
+        launchAndIgnoreFailure(() -> client.performRequest(new Request("DELETE", "/_ingest/pipeline/twitter_pipeline")));
+
+        // DELETE /_template/twitter_template
+        launchAndIgnoreFailure(() -> client.performRequest(new Request("DELETE", "/_template/twitter_template")));
+
+        // DELETE /_index_template/template_1
+        launchAndIgnoreFailure(() -> client.performRequest(new Request("DELETE", "/_index_template/template_1")));
+
+        // DELETE /_component_template/component1
+        launchAndIgnoreFailure(() -> client.performRequest(new Request("DELETE", "/_component_template/component1")));
+        // DELETE /_component_template/component2
+        launchAndIgnoreFailure(() -> client.performRequest(new Request("DELETE", "/_component_template/component2")));
     }
 
     @BeforeClass
@@ -92,7 +110,9 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
 
     protected void testBeyonder(String root,
                                 List<String> indices,
-                                List<String> templates) throws Exception {
+                                List<String> templates,
+                                List<String> componentTemplates,
+                                List<String> indexTemplates) throws Exception {
         logger.info("--> scanning: [{}]", root);
         ElasticsearchBeyonder.start(client, root);
 
@@ -108,6 +128,30 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
             assertThat(allExists, is(true));
         }
 
+        // We can now check if we have the component templates created
+        if (componentTemplates != null) {
+            boolean allExists = true;
+
+            for (String template : componentTemplates) {
+                if (!isComponentTemplateExist(client, template)) {
+                    allExists = false;
+                }
+            }
+            assertThat(allExists, is(true));
+        }
+
+        // We can now check if we have the index templates created
+        if (indexTemplates != null) {
+            boolean allExists = true;
+
+            for (String template : indexTemplates) {
+                if (!isIndexTemplateExist(client, template)) {
+                    allExists = false;
+                }
+            }
+            assertThat(allExists, is(true));
+        }
+
         // We can now check if we have the indices created
         if (indices != null) {
             boolean allExists = true;
@@ -125,8 +169,8 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
     // See https://github.com/dadoonet/elasticsearch-beyonder/issues/2
     @Test
     public void testAliases() throws Exception {
-        IndexElasticsearchUpdater.createIndex(client, "test_aliases", true);
-        AliasElasticsearchUpdater.createAlias(client, "foo", "test_aliases");
+        ElasticsearchIndexUpdater.createIndex(client, SettingsFinder.Defaults.ConfigDir, "test_aliases", true);
+        ElasticsearchAliasUpdater.createAlias(client, "foo", "test_aliases");
         Map<String, Object> response = asMap(client.performRequest(new Request("GET", "/_alias/foo")));
         assertThat(response, hasKey("test_aliases"));
     }
@@ -152,7 +196,7 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
     @Test
 	public void testPipeline() throws Exception {
 		ElasticsearchBeyonder.start(client, "models/pipeline");
-		assertThat(PipelineElasticsearchUpdater.isPipelineExist(client, "twitter_pipeline"), is(true));
+		assertThat(ElasticsearchPipelineUpdater.isPipelineExist(client, "twitter_pipeline"), is(true));
 	}
 
     @Test
@@ -161,7 +205,7 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
         {
             testBeyonder("models/update-settings/step1",
                     singletonList("twitter"),
-                    null);
+                    null, null, null);
             Map<String, Object> oldSettings = asMap(client.performRequest(new Request("GET", "/twitter/_settings")));
             String numberOfReplicas = BeanUtils.getProperty(oldSettings, "twitter.settings.index.number_of_replicas");
             assertThat(numberOfReplicas, equalTo("0"));
@@ -171,7 +215,7 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
         {
             testBeyonder("models/update-settings/step2",
                     singletonList("twitter"),
-                    null);
+                    null, null, null);
             Map<String, Object> settings = asMap(client.performRequest(new Request("GET", "/twitter/_settings")));
             String numberOfReplicas = BeanUtils.getProperty(settings, "twitter.settings.index.number_of_replicas");
             assertThat(numberOfReplicas, equalTo("1"));
@@ -184,7 +228,7 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
         {
             testBeyonder("models/update-mapping/step1",
                     singletonList("twitter"),
-                    null);
+                    null, null, null);
 
             Map<String, Object> mapping = asMap(client.performRequest(new Request("GET", "/twitter/_mappings")));
             String bar = BeanUtils.getProperty(mapping, "twitter.mappings.properties.bar");
@@ -199,7 +243,7 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
         {
             testBeyonder("models/update-mapping/step2",
                     singletonList("twitter"),
-                    null);
+                    null, null, null);
 
             Map<String, Object> mapping = asMap(client.performRequest(new Request("GET", "/twitter/_mappings")));
             String bar = BeanUtils.getProperty(mapping, "twitter.mappings.properties.bar");
@@ -211,6 +255,17 @@ public class BeyonderRestIT extends AbstractBeyonderTest {
         }
     }
 
+    @Test
+    public void testIndexTemplates() throws Exception {
+        // 1 template
+        testBeyonder("models/templatev2",
+                null,
+                null,
+                asList("component1", "component2"),
+                singletonList("template_1"));
+    }
+
+
     private String getMapping(String indexName) throws IOException {
         HttpEntity response = client.performRequest(new Request("GET", indexName + "/_mapping")).getEntity();
         ByteArrayOutputStream out = new ByteArrayOutputStream(Math.toIntExact(response.getContentLength()));
diff --git a/src/test/java/fr/pilato/elasticsearch/tools/BeyonderTransportIT.java b/src/test/java/fr/pilato/elasticsearch/tools/BeyonderTransportIT.java
index a3e0781..22241f9 100644
--- a/src/test/java/fr/pilato/elasticsearch/tools/BeyonderTransportIT.java
+++ b/src/test/java/fr/pilato/elasticsearch/tools/BeyonderTransportIT.java
@@ -20,13 +20,17 @@
 package fr.pilato.elasticsearch.tools;
 
 import org.apache.commons.beanutils.BeanUtils;
+import org.elasticsearch.action.ActionType;
 import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
+import org.elasticsearch.action.admin.indices.template.delete.DeleteComponentTemplateAction;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.RestClient;
 import org.elasticsearch.client.transport.NoNodeAvailableException;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.transport.TransportAddress;
+import org.elasticsearch.index.IndexNotFoundException;
 import org.elasticsearch.transport.client.PreBuiltTransportClient;
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -38,9 +42,12 @@ import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
-import static fr.pilato.elasticsearch.tools.index.IndexElasticsearchUpdater.isIndexExist;
-import static fr.pilato.elasticsearch.tools.template.TemplateElasticsearchUpdater.isTemplateExist;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexUpdater.isIndexExist;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchIndexUpdater.removeIndexInElasticsearch;
+import static fr.pilato.elasticsearch.tools.updaters.ElasticsearchTemplateUpdater.isTemplateExist;
 import static java.util.Collections.singletonList;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
@@ -82,13 +89,18 @@ public class BeyonderTransportIT extends AbstractBeyonderTest {
         }
     }
 
-    @Before
+    @Before @After
     public void cleanCluster() {
-        try {
-            client.admin().indices().prepareDelete("_all").get();
-        } catch (NoNodeAvailableException e) {
-            assumeNoException(e);
-        }
+        // DELETE /twitter
+        launchAndIgnoreFailure(() -> client.admin().indices().prepareDelete("twitter").get());
+        // DELETE /test_aliases
+        launchAndIgnoreFailure(() -> client.admin().indices().prepareDelete("test_aliases").get());
+
+        // DELETE /_ingest/pipeline/twitter_pipeline
+        launchAndIgnoreFailure(() -> client.admin().cluster().prepareDeletePipeline("twitter_pipeline").get());
+
+        // DELETE /_template/twitter_template
+        launchAndIgnoreFailure(() -> client.admin().indices().prepareDeleteTemplate("twitter_template").get());
     }
 
     @Override
@@ -105,7 +117,7 @@ public class BeyonderTransportIT extends AbstractBeyonderTest {
         {
             testBeyonder("models/update-settings/step1",
                     singletonList("twitter"),
-                    null);
+                    null, null, null);
             GetSettingsResponse settings = client.admin().indices().prepareGetSettings("twitter").get();
             String numberOfReplicas = settings.getSetting("twitter", "index.number_of_replicas");
             assertThat(numberOfReplicas, equalTo("0"));
@@ -115,7 +127,7 @@ public class BeyonderTransportIT extends AbstractBeyonderTest {
         {
             testBeyonder("models/update-settings/step2",
                     singletonList("twitter"),
-                    null);
+                    null, null, null);
             GetSettingsResponse settings = client.admin().indices().prepareGetSettings("twitter").get();
             String numberOfReplicas = settings.getSetting("twitter", "index.number_of_replicas");
             assertThat(numberOfReplicas, equalTo("1"));
@@ -128,7 +140,7 @@ public class BeyonderTransportIT extends AbstractBeyonderTest {
         {
             testBeyonder("models/update-mapping/step1",
                     singletonList("twitter"),
-                    null);
+                    null, null, null);
             Map<String, Object> properties = client.admin().indices().prepareGetMappings("twitter").get().getMappings().get("twitter").get("_doc").getSourceAsMap();
             String bar = BeanUtils.getProperty(properties, "properties.bar");
             String foo = BeanUtils.getProperty(properties, "properties.foo");
@@ -142,7 +154,7 @@ public class BeyonderTransportIT extends AbstractBeyonderTest {
         {
             testBeyonder("models/update-mapping/step2",
                     singletonList("twitter"),
-                    null);
+                    null, null, null);
 
             Map<String, Object> properties = client.admin().indices().prepareGetMappings("twitter").get().getMappings().get("twitter").get("_doc").getSourceAsMap();
             String bar = BeanUtils.getProperty(properties, "properties.bar");
@@ -156,7 +168,9 @@ public class BeyonderTransportIT extends AbstractBeyonderTest {
 
     protected void testBeyonder(String root,
                                 List<String> indices,
-                                List<String> templates) throws Exception {
+                                List<String> templates,
+                                List<String> componentTemplates,
+                                List<String> indexTemplates) throws Exception {
         String newRoot = "transport/" + root;
         logger.info("--> scanning: [{}]", newRoot);
         ElasticsearchBeyonder.start(client, newRoot);
diff --git a/src/test/java/fr/pilato/elasticsearch/tools/BeyonderUnitTest.java b/src/test/java/fr/pilato/elasticsearch/tools/BeyonderUnitTest.java
index 0e26347..4997e25 100644
--- a/src/test/java/fr/pilato/elasticsearch/tools/BeyonderUnitTest.java
+++ b/src/test/java/fr/pilato/elasticsearch/tools/BeyonderUnitTest.java
@@ -19,9 +19,8 @@
 
 package fr.pilato.elasticsearch.tools;
 
-import fr.pilato.elasticsearch.tools.index.IndexSettingsReader;
-import fr.pilato.elasticsearch.tools.template.TemplateFinder;
-import fr.pilato.elasticsearch.tools.template.TemplateSettingsReader;
+import fr.pilato.elasticsearch.tools.util.ResourceList;
+import fr.pilato.elasticsearch.tools.util.SettingsFinder;
 import org.junit.Test;
 
 import java.io.ByteArrayInputStream;
@@ -29,8 +28,10 @@ import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 
-import static fr.pilato.elasticsearch.tools.index.IndexFinder.findIndexNames;
+import static fr.pilato.elasticsearch.tools.util.SettingsReader.getJsonContent;
+import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.emptyIterable;
@@ -42,14 +43,12 @@ public class BeyonderUnitTest extends AbstractBeyonderTest {
 
     protected void testBeyonder(String root,
                                 List<String> indices,
-                                List<String> templates) throws IOException, URISyntaxException {
+                                List<String> templates,
+                                List<String> componentTemplates,
+                                List<String> indexTemplates) throws IOException, URISyntaxException {
         logger.info("--> scanning: [{}]", root);
-        List<String> indexNames;
-        if (root == null) {
-            indexNames = findIndexNames();
-        } else {
-            indexNames = findIndexNames(root);
-        }
+
+        List<String> indexNames = ResourceList.findIndexNames(root);
         logger.info("  --> indices found: {}", indexNames);
 
         if (indices != null) {
@@ -61,29 +60,54 @@ public class BeyonderUnitTest extends AbstractBeyonderTest {
                 logger.debug("  --> index [{}]:", indexName);
                 assertThat(indexName, is(indices.get(iIndex)));
 
-                String settings = IndexSettingsReader.readSettings(root, indexName);
+                String settings = getJsonContent(root, indexName, SettingsFinder.Defaults.IndexSettingsFileName);
                 logger.debug("    --> Settings: [{}]", settings);
             }
         } else {
             assertThat(indexNames, emptyIterable());
         }
 
-        List<String> templateNames = TemplateFinder.findTemplates(root);
-        logger.info("  --> templates found: {}", templateNames);
+        check(ResourceList.getResourceNames(root, SettingsFinder.Defaults.TemplateDir), templates, (name) -> {
+            try {
+                return getJsonContent(root, SettingsFinder.Defaults.TemplateDir, name);
+            } catch (IOException e) {
+                throw new RuntimeException("Our test is failing...");
+            }
+        });
+        check(ResourceList.getResourceNames(root, SettingsFinder.Defaults.ComponentTemplatesDir), componentTemplates, (name) -> {
+            try {
+                return getJsonContent(root, SettingsFinder.Defaults.ComponentTemplatesDir, name);
+            } catch (IOException e) {
+                throw new RuntimeException("Our test is failing...");
+            }
+        });
+        check(ResourceList.getResourceNames(root, SettingsFinder.Defaults.IndexTemplatesDir), indexTemplates, (name) -> {
+            try {
+                return getJsonContent(root, SettingsFinder.Defaults.IndexTemplatesDir, name);
+            } catch (IOException e) {
+                throw new RuntimeException("Our test is failing...");
+            }
+        });
+    }
+
+    private void check(List<String> names, List<String> expectedNames,
+                       Function<String, String> reader) {
+        logger.info("  --> names found: {}", names);
 
-        if (templates != null) {
-            assertThat(templateNames, hasSize(templates.size()));
-            for (int iTemplate = 0; iTemplate < templateNames.size(); iTemplate++) {
-                String templateName = templateNames.get(iTemplate);
-                logger.debug("    --> template: [{}]", templateName);
-                assertThat(templateName, is(templates.get(iTemplate)));
+        if (expectedNames != null) {
+            assertThat(names, hasSize(expectedNames.size()));
+            for (int iTemplate = 0; iTemplate < names.size(); iTemplate++) {
+                String name = names.get(iTemplate);
+                logger.debug("    --> name: [{}]", name);
+                assertThat(name, is(expectedNames.get(iTemplate)));
 
-                String template = TemplateSettingsReader.readTemplate(root, templateName);
-                logger.debug("      --> Template: [{}]", template);
+                String json = reader.apply(name);
+                logger.debug("      --> Json: [{}]", json);
             }
         } else {
-            assertThat(templateNames, emptyIterable());
+            assertThat(names, emptyIterable());
         }
+
     }
 
     @Test
@@ -95,7 +119,7 @@ public class BeyonderUnitTest extends AbstractBeyonderTest {
         String indexName = "twitter";
 
         // when: this settings file is read
-        String settings = IndexSettingsReader.readSettings(folder, indexName);
+        String settings = getJsonContent(folder, indexName, SettingsFinder.Defaults.IndexSettingsFileName);
         Map<String, Object> settingsMap = JsonUtil.asMap(new ByteArrayInputStream(settings.getBytes()));
 
         // then: the variables got replaced by environment variables of the same name
@@ -109,12 +133,12 @@ public class BeyonderUnitTest extends AbstractBeyonderTest {
         // 1 _settings
         testBeyonder("models/update-mapping/step1",
                 singletonList("twitter"),
-                null);
+                null, null, null);
 
         // 2 _update_mapping
         testBeyonder("models/update-mapping/step2",
                 singletonList("twitter"),
-                null);
+                null, null, null);
     }
 
     @Test
@@ -122,11 +146,21 @@ public class BeyonderUnitTest extends AbstractBeyonderTest {
         // 1 _settings
         testBeyonder("models/update-settings/step1",
                 singletonList("twitter"),
-                null);
+                null, null, null);
 
         // 2 _update_settings
         testBeyonder("models/update-settings/step2",
                 singletonList("twitter"),
-                null);
+                null, null, null);
+    }
+
+    @Test
+    public void testIndexTemplates() throws Exception {
+        // 1 template
+        testBeyonder("models/templatev2",
+                null,
+                null,
+                asList("component1", "component2"),
+                singletonList("template_1"));
     }
 }
diff --git a/src/test/java/fr/pilato/elasticsearch/tools/ResourceListTest.java b/src/test/java/fr/pilato/elasticsearch/tools/ResourceListTest.java
index ded368a..9697311 100644
--- a/src/test/java/fr/pilato/elasticsearch/tools/ResourceListTest.java
+++ b/src/test/java/fr/pilato/elasticsearch/tools/ResourceListTest.java
@@ -19,6 +19,8 @@
 
 package fr.pilato.elasticsearch.tools;
 
+import fr.pilato.elasticsearch.tools.util.ResourceList;
+import fr.pilato.elasticsearch.tools.util.SettingsReader;
 import org.junit.Test;
 
 import java.io.IOException;
diff --git a/src/test/resources/models/templatev2/_component_templates/component1.json b/src/test/resources/models/templatev2/_component_templates/component1.json
new file mode 100644
index 0000000..56c1bd8
--- /dev/null
+++ b/src/test/resources/models/templatev2/_component_templates/component1.json
@@ -0,0 +1,11 @@
+{
+    "template": {
+        "mappings": {
+            "properties": {
+                "@timestamp": {
+                    "type": "date"
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/resources/models/templatev2/_component_templates/component2.json b/src/test/resources/models/templatev2/_component_templates/component2.json
new file mode 100644
index 0000000..bd5fa4e
--- /dev/null
+++ b/src/test/resources/models/templatev2/_component_templates/component2.json
@@ -0,0 +1,14 @@
+{
+    "template": {
+        "mappings": {
+            "runtime": {
+                "day_of_week": {
+                    "type": "keyword",
+                    "script": {
+                        "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/resources/models/templatev2/_index_templates/template_1.json b/src/test/resources/models/templatev2/_index_templates/template_1.json
new file mode 100644
index 0000000..157b918
--- /dev/null
+++ b/src/test/resources/models/templatev2/_index_templates/template_1.json
@@ -0,0 +1,31 @@
+{
+    "index_patterns": ["te*", "bar*"],
+    "template": {
+        "settings": {
+            "number_of_shards": 1
+        },
+        "mappings": {
+            "_source": {
+                "enabled": true
+            },
+            "properties": {
+                "host_name": {
+                    "type": "keyword"
+                },
+                "created_at": {
+                    "type": "date",
+                    "format": "EEE MMM dd HH:mm:ss Z yyyy"
+                }
+            }
+        },
+        "aliases": {
+            "mydata": { }
+        }
+    },
+    "priority": 500,
+    "composed_of": ["component1", "component2"],
+    "version": 3,
+    "_meta": {
+        "description": "my custom"
+    }
+}