Browse Source

Allows multiple patterns to be specified for index templates (#21009)

* Allows for an array of index template patterns to be provided to an
index template, and rename the field from 'template' to 'index_pattern'.

Closes #20690
Alexander Lin 9 years ago
parent
commit
0219a211d3
34 changed files with 528 additions and 188 deletions
  1. 44 12
      core/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequest.java
  2. 15 3
      core/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequestBuilder.java
  3. 1 1
      core/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutIndexTemplateAction.java
  4. 1 1
      core/src/main/java/org/elasticsearch/cluster/ClusterState.java
  5. 46 24
      core/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateMetaData.java
  6. 9 6
      core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java
  7. 24 21
      core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexTemplateService.java
  8. 13 1
      core/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutIndexTemplateAction.java
  9. 2 2
      core/src/main/java/org/elasticsearch/rest/action/cat/RestTemplatesAction.java
  10. 5 1
      core/src/test/java/org/elasticsearch/VersionTests.java
  11. 10 9
      core/src/test/java/org/elasticsearch/action/admin/indices/template/put/MetaDataIndexTemplateServiceTests.java
  12. 69 0
      core/src/test/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequestTests.java
  13. 2 1
      core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java
  14. 1 1
      core/src/test/java/org/elasticsearch/cluster/ClusterStateDiffIT.java
  15. 4 2
      core/src/test/java/org/elasticsearch/cluster/SimpleClusterStateIT.java
  16. 0 1
      core/src/test/java/org/elasticsearch/cluster/metadata/IndexMetaDataTests.java
  17. 81 0
      core/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateMetaDataTests.java
  18. 5 3
      core/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetaDataTests.java
  19. 3 2
      core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java
  20. 2 1
      core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java
  21. 3 2
      core/src/test/java/org/elasticsearch/indices/template/IndexTemplateBlocksIT.java
  22. 74 30
      core/src/test/java/org/elasticsearch/indices/template/SimpleIndexTemplateIT.java
  23. 2 2
      core/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java
  24. 5 5
      docs/reference/cat.asciidoc
  25. 7 7
      docs/reference/indices/templates.asciidoc
  26. 1 1
      docs/reference/mapping/dynamic-mapping.asciidoc
  27. 1 1
      docs/reference/mapping/dynamic/default-mapping.asciidoc
  28. 1 1
      docs/reference/mapping/dynamic/templates.asciidoc
  29. 58 27
      rest-api-spec/src/main/resources/rest-api-spec/test/cat.templates/10_basic.yaml
  30. 1 1
      rest-api-spec/src/main/resources/rest-api-spec/test/indices.exists_template/10_basic.yaml
  31. 5 5
      rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_template/10_basic.yaml
  32. 31 12
      rest-api-spec/src/main/resources/rest-api-spec/test/indices.put_template/10_basic.yaml
  33. 1 1
      test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java
  34. 1 1
      test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java

+ 44 - 12
core/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequest.java

@@ -20,6 +20,7 @@ package org.elasticsearch.action.admin.indices.template.put;
 
 import org.elasticsearch.ElasticsearchGenerationException;
 import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.IndicesRequest;
 import org.elasticsearch.action.admin.indices.alias.Alias;
@@ -32,6 +33,8 @@ import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.collect.MapBuilder;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.logging.DeprecationLogger;
+import org.elasticsearch.common.logging.Loggers;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
@@ -41,10 +44,13 @@ import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import static org.elasticsearch.action.ValidateActions.addValidationError;
 import static org.elasticsearch.common.settings.Settings.readSettingsFromStream;
@@ -56,11 +62,15 @@ import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
  */
 public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateRequest> implements IndicesRequest {
 
+    private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(PutIndexTemplateRequest.class));
+
+    public static final Version V_5_1_0 = Version.fromId(5010099);
+
     private String name;
 
     private String cause = "";
 
-    private String template;
+    private List<String> indexPatterns;
 
     private int order;
 
@@ -92,8 +102,8 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
         if (name == null) {
             validationException = addValidationError("name is missing", validationException);
         }
-        if (template == null) {
-            validationException = addValidationError("template is missing", validationException);
+        if (indexPatterns == null || indexPatterns.size() == 0) {
+            validationException = addValidationError("pattern is missing", validationException);
         }
         return validationException;
     }
@@ -113,13 +123,13 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
         return this.name;
     }
 
-    public PutIndexTemplateRequest template(String template) {
-        this.template = template;
+    public PutIndexTemplateRequest patterns(List<String> indexPatterns) {
+        this.indexPatterns = indexPatterns;
         return this;
     }
 
-    public String template() {
-        return this.template;
+    public List<String> patterns() {
+        return this.indexPatterns;
     }
 
     public PutIndexTemplateRequest order(int order) {
@@ -286,7 +296,20 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
         for (Map.Entry<String, Object> entry : source.entrySet()) {
             String name = entry.getKey();
             if (name.equals("template")) {
-                template(entry.getValue().toString());
+                // This is needed to allow for bwc (beats, logstash) with pre-5.0 templates (#21009)
+                if(entry.getValue() instanceof String) {
+                    DEPRECATION_LOGGER.deprecated("Deprecated field [template] used, replaced by [index_patterns]");
+                    patterns(Collections.singletonList((String) entry.getValue()));
+                }
+            } else if (name.equals("index_patterns")) {
+                if(entry.getValue() instanceof String) {
+                    patterns(Collections.singletonList((String) entry.getValue()));
+                } else if (entry.getValue() instanceof List) {
+                    List<String> elements = ((List<?>) entry.getValue()).stream().map(Object::toString).collect(Collectors.toList());
+                    patterns(elements);
+                } else {
+                    throw new IllegalArgumentException("Malformed [template] value, should be a string or a list of strings");
+                }
             } else if (name.equals("order")) {
                 order(XContentMapValues.nodeIntegerValue(entry.getValue(), order()));
             } else if ("version".equals(name)) {
@@ -295,7 +318,7 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
                 }
                 version((Integer)entry.getValue());
             } else if (name.equals("settings")) {
-                if (!(entry.getValue() instanceof Map)) {
+                if ((entry.getValue() instanceof Map) == false) {
                     throw new IllegalArgumentException("Malformed [settings] section, should include an inner object");
                 }
                 settings((Map<String, Object>) entry.getValue());
@@ -436,7 +459,7 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
 
     @Override
     public String[] indices() {
-        return new String[]{template};
+        return indexPatterns.toArray(new String[indexPatterns.size()]);
     }
 
     @Override
@@ -449,7 +472,12 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
         super.readFrom(in);
         cause = in.readString();
         name = in.readString();
-        template = in.readString();
+
+        if (in.getVersion().onOrAfter(V_5_1_0)) {
+            indexPatterns = in.readList(StreamInput::readString);
+        } else {
+            indexPatterns = Collections.singletonList(in.readString());
+        }
         order = in.readInt();
         create = in.readBoolean();
         settings = readSettingsFromStream(in);
@@ -475,7 +503,11 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
         super.writeTo(out);
         out.writeString(cause);
         out.writeString(name);
-        out.writeString(template);
+        if (out.getVersion().onOrAfter(V_5_1_0)) {
+            out.writeStringList(indexPatterns);
+        } else {
+            out.writeString(indexPatterns.size() > 0 ? indexPatterns.get(0) : "");
+        }
         out.writeInt(order);
         out.writeBoolean(create);
         writeSettingsToStream(settings, out);

+ 15 - 3
core/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequestBuilder.java

@@ -25,6 +25,8 @@ import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 public class PutIndexTemplateRequestBuilder
@@ -39,10 +41,20 @@ public class PutIndexTemplateRequestBuilder
     }
 
     /**
-     * Sets the template match expression that will be used to match on indices created.
+     * Sets the match expression that will be used to match on indices created.
+     *
+     * @deprecated Replaced by {@link #setPatterns(List)}
+     */
+    @Deprecated
+    public PutIndexTemplateRequestBuilder setTemplate(String indexPattern) {
+        return setPatterns(Collections.singletonList(indexPattern));
+    }
+
+    /**
+     * Sets the match expression that will be used to match on indices created.
      */
-    public PutIndexTemplateRequestBuilder setTemplate(String template) {
-        request.template(template);
+    public PutIndexTemplateRequestBuilder setPatterns(List<String> indexPatterns) {
+        request.patterns(indexPatterns);
         return this;
     }
 

+ 1 - 1
core/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutIndexTemplateAction.java

@@ -79,7 +79,7 @@ public class TransportPutIndexTemplateAction extends TransportMasterNodeAction<P
         templateSettingsBuilder.put(request.settings()).normalizePrefix(IndexMetaData.INDEX_SETTING_PREFIX);
         indexScopedSettings.validate(templateSettingsBuilder);
         indexTemplateService.putTemplate(new MetaDataIndexTemplateService.PutRequest(cause, request.name())
-                .template(request.template())
+                .patterns(request.patterns())
                 .order(request.order())
                 .settings(templateSettingsBuilder.build())
                 .mappings(request.mappings())

+ 1 - 1
core/src/main/java/org/elasticsearch/cluster/ClusterState.java

@@ -395,7 +395,7 @@ public class ClusterState implements ToXContent, Diffable<ClusterState> {
                 IndexTemplateMetaData templateMetaData = cursor.value;
                 builder.startObject(templateMetaData.name());
 
-                builder.field("template", templateMetaData.template());
+                builder.field("index_patterns", templateMetaData.patterns());
                 builder.field("order", templateMetaData.order());
 
                 builder.startObject("settings");

+ 46 - 24
core/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateMetaData.java

@@ -29,6 +29,8 @@ import org.elasticsearch.common.collect.MapBuilder;
 import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.logging.DeprecationLogger;
+import org.elasticsearch.common.logging.Loggers;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.loader.SettingsLoader;
 import org.elasticsearch.common.util.set.Sets;
@@ -38,13 +40,19 @@ import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
 public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaData> {
 
+    public static final Version V_5_1_0 = Version.fromId(5010099);
+
     public static final IndexTemplateMetaData PROTO = IndexTemplateMetaData.builder("").build();
+    private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(IndexTemplateMetaData.class));
 
     private final String name;
 
@@ -56,7 +64,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
      * <pre><code>
      * PUT /_template/my_template
      * {
-     *   "template": "my_index-*",
+     *   "index_patterns": ["my_index-*"],
      *   "mappings": { ... },
      *   "version": 1
      * }
@@ -70,7 +78,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
     @Nullable
     private final Integer version;
 
-    private final String template;
+    private final List<String> patterns;
 
     private final Settings settings;
 
@@ -82,14 +90,14 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
     private final ImmutableOpenMap<String, IndexMetaData.Custom> customs;
 
     public IndexTemplateMetaData(String name, int order, Integer version,
-                                 String template, Settings settings,
+                                 List<String> patterns, Settings settings,
                                  ImmutableOpenMap<String, CompressedXContent> mappings,
                                  ImmutableOpenMap<String, AliasMetaData> aliases,
                                  ImmutableOpenMap<String, IndexMetaData.Custom> customs) {
         this.name = name;
         this.order = order;
         this.version = version;
-        this.template = template;
+        this.patterns= patterns;
         this.settings = settings;
         this.mappings = mappings;
         this.aliases = aliases;
@@ -122,12 +130,12 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
         return this.name;
     }
 
-    public String template() {
-        return this.template;
+    public List<String> patterns() {
+        return this.patterns;
     }
 
-    public String getTemplate() {
-        return this.template;
+    public List<String> getPatterns() {
+        return this.patterns;
     }
 
     public Settings settings() {
@@ -182,7 +190,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
         if (!mappings.equals(that.mappings)) return false;
         if (!name.equals(that.name)) return false;
         if (!settings.equals(that.settings)) return false;
-        if (!template.equals(that.template)) return false;
+        if (!patterns.equals(that.patterns)) return false;
 
         return Objects.equals(version, that.version);
     }
@@ -192,7 +200,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
         int result = name.hashCode();
         result = 31 * result + order;
         result = 31 * result + Objects.hashCode(version);
-        result = 31 * result + template.hashCode();
+        result = 31 * result + patterns.hashCode();
         result = 31 * result + settings.hashCode();
         result = 31 * result + mappings.hashCode();
         return result;
@@ -202,7 +210,11 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
     public IndexTemplateMetaData readFrom(StreamInput in) throws IOException {
         Builder builder = new Builder(in.readString());
         builder.order(in.readInt());
-        builder.template(in.readString());
+        if (in.getVersion().onOrAfter(V_5_1_0)) {
+            builder.patterns(in.readList(StreamInput::readString));
+        } else {
+            builder.patterns(Collections.singletonList(in.readString()));
+        }
         builder.settings(Settings.readSettingsFromStream(in));
         int mappingsSize = in.readVInt();
         for (int i = 0; i < mappingsSize; i++) {
@@ -229,7 +241,11 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
     public void writeTo(StreamOutput out) throws IOException {
         out.writeString(name);
         out.writeInt(order);
-        out.writeString(template);
+        if (out.getVersion().onOrAfter(V_5_1_0)) {
+            out.writeStringList(patterns);
+        } else {
+            out.writeString(patterns.size() > 0 ? patterns.get(0) : "");
+        }
         Settings.writeSettingsToStream(settings, out);
         out.writeVInt(mappings.size());
         for (ObjectObjectCursor<String, CompressedXContent> cursor : mappings) {
@@ -252,7 +268,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
 
     public static class Builder {
 
-        private static final Set<String> VALID_FIELDS = Sets.newHashSet("template", "order", "mappings", "settings");
+        private static final Set<String> VALID_FIELDS = Sets.newHashSet("template", "order", "mappings", "settings", "index_patterns");
         static {
             VALID_FIELDS.addAll(IndexMetaData.customPrototypes.keySet());
         }
@@ -263,7 +279,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
 
         private Integer version;
 
-        private String template;
+        private List<String> indexPatterns;
 
         private Settings settings = Settings.Builder.EMPTY_SETTINGS;
 
@@ -284,7 +300,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
             this.name = indexTemplateMetaData.name();
             order(indexTemplateMetaData.order());
             version(indexTemplateMetaData.version());
-            template(indexTemplateMetaData.template());
+            patterns(indexTemplateMetaData.patterns());
             settings(indexTemplateMetaData.settings());
 
             mappings = ImmutableOpenMap.builder(indexTemplateMetaData.mappings());
@@ -302,14 +318,11 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
             return this;
         }
 
-        public Builder template(String template) {
-            this.template = template;
+        public Builder patterns(List<String> indexPatterns) {
+            this.indexPatterns = indexPatterns;
             return this;
         }
 
-        public String template() {
-            return template;
-        }
 
         public Builder settings(Settings.Builder settings) {
             this.settings = settings.build();
@@ -361,7 +374,8 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
         }
 
         public IndexTemplateMetaData build() {
-            return new IndexTemplateMetaData(name, order, version, template, settings, mappings.build(), aliases.build(), customs.build());
+            return new IndexTemplateMetaData(name, order, version, indexPatterns, settings, mappings.build(),
+                aliases.build(), customs.build());
         }
 
         @SuppressWarnings("unchecked")
@@ -373,7 +387,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
             if (indexTemplateMetaData.version() != null) {
                 builder.field("version", indexTemplateMetaData.version());
             }
-            builder.field("template", indexTemplateMetaData.template());
+            builder.field("index_patterns", indexTemplateMetaData.patterns());
 
             builder.startObject("settings");
             indexTemplateMetaData.settings().toXContent(builder, params);
@@ -478,10 +492,18 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
                                 }
                             }
                         }
+                    } else if ("index_patterns".equals(currentFieldName)) {
+                        List<String> index_patterns = new ArrayList<>();
+                        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
+                            index_patterns.add(parser.text());
+                        }
+                        builder.patterns(index_patterns);
                     }
                 } else if (token.isValue()) {
-                    if ("template".equals(currentFieldName)) {
-                        builder.template(parser.text());
+                    // Prior to 5.1.0, elasticsearch only supported a single index pattern called `template` (#21009)
+                    if("template".equals(currentFieldName)) {
+                        DEPRECATION_LOGGER.deprecated("Deprecated field [template] used, replaced by [index_patterns]");
+                        builder.patterns(Collections.singletonList(parser.text()));
                     } else if ("order".equals(currentFieldName)) {
                         builder.order(parser.intValue());
                     } else if ("version".equals(currentFieldName)) {

+ 9 - 6
core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java

@@ -464,21 +464,24 @@ public class MetaDataCreateIndexService extends AbstractComponent {
     }
 
     private List<IndexTemplateMetaData> findTemplates(CreateIndexClusterStateUpdateRequest request, ClusterState state) throws IOException {
-        List<IndexTemplateMetaData> templates = new ArrayList<>();
+        List<IndexTemplateMetaData> templateMetadata = new ArrayList<>();
         for (ObjectCursor<IndexTemplateMetaData> cursor : state.metaData().templates().values()) {
-            IndexTemplateMetaData template = cursor.value;
-            if (Regex.simpleMatch(template.template(), request.index())) {
-                templates.add(template);
+            IndexTemplateMetaData metadata = cursor.value;
+            for (String template: metadata.patterns()) {
+                if (Regex.simpleMatch(template, request.index())) {
+                    templateMetadata.add(metadata);
+                    break;
+                }
             }
         }
 
-        CollectionUtil.timSort(templates, new Comparator<IndexTemplateMetaData>() {
+        CollectionUtil.timSort(templateMetadata, new Comparator<IndexTemplateMetaData>() {
             @Override
             public int compare(IndexTemplateMetaData o1, IndexTemplateMetaData o2) {
                 return o2.order() - o1.order();
             }
         });
-        return templates;
+        return templateMetadata;
     }
 
     private void validate(CreateIndexClusterStateUpdateRequest request, ClusterState state) {

+ 24 - 21
core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexTemplateService.java

@@ -131,7 +131,7 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
             listener.onFailure(new IllegalArgumentException("index_template must provide a name"));
             return;
         }
-        if (request.template == null) {
+        if (request.indexPatterns == null) {
             listener.onFailure(new IllegalArgumentException("index_template must provide a template"));
             return;
         }
@@ -209,7 +209,7 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
 
             templateBuilder.order(request.order);
             templateBuilder.version(request.version);
-            templateBuilder.template(request.template);
+            templateBuilder.patterns(request.indexPatterns);
             templateBuilder.settings(request.settings);
 
             Map<String, Map<String, Object>> mappingsForValidation = new HashMap<>();
@@ -248,20 +248,22 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
         if (!request.name.toLowerCase(Locale.ROOT).equals(request.name)) {
             validationErrors.add("name must be lower cased");
         }
-        if (request.template.contains(" ")) {
-            validationErrors.add("template must not contain a space");
-        }
-        if (request.template.contains(",")) {
-            validationErrors.add("template must not contain a ','");
-        }
-        if (request.template.contains("#")) {
-            validationErrors.add("template must not contain a '#'");
-        }
-        if (request.template.startsWith("_")) {
-            validationErrors.add("template must not start with '_'");
-        }
-        if (!Strings.validFileNameExcludingAstrix(request.template)) {
-            validationErrors.add("template must not contain the following characters " + Strings.INVALID_FILENAME_CHARS);
+        for(String indexPattern : request.indexPatterns) {
+            if (indexPattern.contains(" ")) {
+                validationErrors.add("template must not contain a space");
+            }
+            if (indexPattern.contains(",")) {
+                validationErrors.add("template must not contain a ','");
+            }
+            if (indexPattern.contains("#")) {
+                validationErrors.add("template must not contain a '#'");
+            }
+            if (indexPattern.startsWith("_")) {
+                validationErrors.add("template must not start with '_'");
+            }
+            if (!Strings.validFileNameExcludingAstrix(indexPattern)) {
+                validationErrors.add("template must not contain the following characters " + Strings.INVALID_FILENAME_CHARS);
+            }
         }
 
         try {
@@ -283,8 +285,9 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
         for (Alias alias : request.aliases) {
             //we validate the alias only partially, as we don't know yet to which index it'll get applied to
             aliasValidator.validateAliasStandalone(alias);
-            if (request.template.equals(alias.name())) {
-                throw new IllegalArgumentException("Alias [" + alias.name() + "] cannot be the same as the template pattern [" + request.template + "]");
+            if (request.indexPatterns.contains(alias.name())) {
+                throw new IllegalArgumentException("Alias [" + alias.name() +
+                    "] cannot be the same as any pattern in [" + String.join(", ", request.indexPatterns) + "]");
             }
         }
     }
@@ -302,7 +305,7 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
         boolean create;
         int order;
         Integer version;
-        String template;
+        List<String> indexPatterns;
         Settings settings = Settings.Builder.EMPTY_SETTINGS;
         Map<String, String> mappings = new HashMap<>();
         List<Alias> aliases = new ArrayList<>();
@@ -320,8 +323,8 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
             return this;
         }
 
-        public PutRequest template(String template) {
-            this.template = template;
+        public PutRequest patterns(List<String> indexPatterns) {
+            this.indexPatterns = indexPatterns;
             return this;
         }
 

+ 13 - 1
core/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutIndexTemplateAction.java

@@ -21,7 +21,10 @@ package org.elasticsearch.rest.action.admin.indices;
 
 import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
 import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.DeprecationLogger;
+import org.elasticsearch.common.logging.Loggers;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
@@ -29,9 +32,13 @@ import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.action.AcknowledgedRestListener;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
 
 public class RestPutIndexTemplateAction extends BaseRestHandler {
 
+    private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(RestPutIndexTemplateAction.class));
+
     @Inject
     public RestPutIndexTemplateAction(Settings settings, RestController controller) {
         super(settings);
@@ -42,7 +49,12 @@ public class RestPutIndexTemplateAction extends BaseRestHandler {
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
         PutIndexTemplateRequest putRequest = new PutIndexTemplateRequest(request.param("name"));
-        putRequest.template(request.param("template", putRequest.template()));
+        if (request.hasParam("template")) {
+            DEPRECATION_LOGGER.deprecated("Deprecated parameter[template] used, replaced by [index_patterns]");
+            putRequest.patterns(Collections.singletonList(request.param("template")));
+        } else {
+            putRequest.patterns(Arrays.asList(request.paramAsStringArray("index_patterns", Strings.EMPTY_ARRAY)));
+        }
         putRequest.order(request.paramAsInt("order", putRequest.order()));
         putRequest.masterNodeTimeout(request.paramAsTime("master_timeout", putRequest.masterNodeTimeout()));
         putRequest.create(request.paramAsBoolean("create", false));

+ 2 - 2
core/src/main/java/org/elasticsearch/rest/action/cat/RestTemplatesAction.java

@@ -70,7 +70,7 @@ public class RestTemplatesAction extends AbstractCatAction {
         Table table = new Table();
         table.startHeaders();
         table.addCell("name", "alias:n;desc:template name");
-        table.addCell("template", "alias:t;desc:template pattern string");
+        table.addCell("index_patterns", "alias:t;desc:template index patterns");
         table.addCell("order", "alias:o;desc:template application order number");
         table.addCell("version", "alias:v;desc:version");
         table.endHeaders();
@@ -85,7 +85,7 @@ public class RestTemplatesAction extends AbstractCatAction {
             if (patternString == null || Regex.simpleMatch(patternString, indexData.name())) {
                 table.startRow();
                 table.addCell(indexData.name());
-                table.addCell(indexData.getTemplate());
+                table.addCell("[" + String.join(", ", indexData.patterns()) + "]");
                 table.addCell(indexData.getOrder());
                 table.addCell(indexData.getVersion());
                 table.endRow();

+ 5 - 1
core/src/test/java/org/elasticsearch/VersionTests.java

@@ -20,7 +20,9 @@
 package org.elasticsearch;
 
 import org.elasticsearch.action.ShardValidateQueryRequestTests;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
+import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
 import org.elasticsearch.common.lucene.Lucene;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.query.QueryStringQueryBuilder;
@@ -275,9 +277,11 @@ public class VersionTests extends ESTestCase {
         assertUnknownVersion(V_20_0_0_UNRELEASED);
         expectThrows(AssertionError.class, () -> assertUnknownVersion(Version.CURRENT));
         assertUnknownVersion(AliasFilter.V_5_1_0); // once we released 5.1.0 and it's added to Version.java we need to remove this constant
+        assertUnknownVersion(IndexTemplateMetaData.V_5_1_0);
         assertUnknownVersion(OsStats.V_5_1_0); // once we released 5.1.0 and it's added to Version.java we need to remove this constant
-        assertUnknownVersion(SimpleQueryStringBuilder.V_5_1_0_UNRELEASED);
+        assertUnknownVersion(PutIndexTemplateRequest.V_5_1_0);
         assertUnknownVersion(QueryStringQueryBuilder.V_5_1_0_UNRELEASED);
+        assertUnknownVersion(SimpleQueryStringBuilder.V_5_1_0_UNRELEASED);
         // once we released 5.0.0 and it's added to Version.java we need to remove this constant
         assertUnknownVersion(Script.V_5_1_0_UNRELEASED);
         // once we released 5.0.0 and it's added to Version.java we need to remove this constant

+ 10 - 9
core/src/test/java/org/elasticsearch/action/admin/indices/template/put/MetaDataIndexTemplateServiceTests.java

@@ -35,6 +35,7 @@ import org.elasticsearch.indices.InvalidIndexTemplateException;
 import org.elasticsearch.test.ESSingleNodeTestCase;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -50,7 +51,7 @@ import static org.hamcrest.CoreMatchers.instanceOf;
 public class MetaDataIndexTemplateServiceTests extends ESSingleNodeTestCase {
     public void testIndexTemplateInvalidNumberOfShards() {
         PutRequest request = new PutRequest("test", "test_shards");
-        request.template("test_shards*");
+        request.patterns(Collections.singletonList("test_shards*"));
 
         Map<String, Object> map = new HashMap<>();
         map.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, "0");
@@ -69,7 +70,7 @@ public class MetaDataIndexTemplateServiceTests extends ESSingleNodeTestCase {
 
     public void testIndexTemplateValidationAccumulatesValidationErrors() {
         PutRequest request = new PutRequest("test", "putTemplate shards");
-        request.template("_test_shards*");
+        request.patterns(Collections.singletonList("_test_shards*"));
 
         Map<String, Object> map = new HashMap<>();
         map.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, "0");
@@ -86,18 +87,18 @@ public class MetaDataIndexTemplateServiceTests extends ESSingleNodeTestCase {
 
     public void testIndexTemplateWithAliasNameEqualToTemplatePattern() {
         PutRequest request = new PutRequest("api", "foobar_template");
-        request.template("foobar");
+        request.patterns(Arrays.asList("foo", "foobar"));
         request.aliases(Collections.singleton(new Alias("foobar")));
 
         List<Throwable> errors = putTemplate(request);
         assertThat(errors.size(), equalTo(1));
         assertThat(errors.get(0), instanceOf(IllegalArgumentException.class));
-        assertThat(errors.get(0).getMessage(), equalTo("Alias [foobar] cannot be the same as the template pattern [foobar]"));
+        assertThat(errors.get(0).getMessage(), equalTo("Alias [foobar] cannot be the same as any pattern in [foo, foobar]"));
     }
 
     public void testIndexTemplateWithValidateEmptyMapping() throws Exception {
         PutRequest request = new PutRequest("api", "validate_template");
-        request.template("validate_template");
+        request.patterns(Collections.singletonList("validate_template"));
         request.putMapping("type1", "{}");
 
         List<Throwable> errors = putTemplateDetail(request);
@@ -108,7 +109,7 @@ public class MetaDataIndexTemplateServiceTests extends ESSingleNodeTestCase {
 
     public void testIndexTemplateWithValidateMapping() throws Exception {
         PutRequest request = new PutRequest("api", "validate_template");
-        request.template("te*");
+        request.patterns(Collections.singletonList("te*"));
         request.putMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
             .startObject("field2").field("type", "string").field("analyzer", "custom_1").endObject()
             .endObject().endObject().endObject().string());
@@ -121,7 +122,7 @@ public class MetaDataIndexTemplateServiceTests extends ESSingleNodeTestCase {
 
     public void testBrokenMapping() throws Exception {
         PutRequest request = new PutRequest("api", "broken_mapping");
-        request.template("te*");
+        request.patterns(Collections.singletonList("te*"));
         request.putMapping("type1", "abcde");
 
         List<Throwable> errors = putTemplateDetail(request);
@@ -132,7 +133,7 @@ public class MetaDataIndexTemplateServiceTests extends ESSingleNodeTestCase {
 
     public void testBlankMapping() throws Exception {
         PutRequest request = new PutRequest("api", "blank_mapping");
-        request.template("te*");
+        request.patterns(Collections.singletonList("te*"));
         request.putMapping("type1", "{}");
 
         List<Throwable> errors = putTemplateDetail(request);
@@ -144,7 +145,7 @@ public class MetaDataIndexTemplateServiceTests extends ESSingleNodeTestCase {
     public void testAliasInvalidFilterInvalidJson() throws Exception {
         //invalid json: put index template fails
         PutRequest request = new PutRequest("api", "blank_mapping");
-        request.template("te*");
+        request.patterns(Collections.singletonList("te*"));
         request.putMapping("type1", "{}");
         Set<Alias> aliases = new HashSet<>();
         aliases.add(new Alias("invalid_alias").filter("abcde"));

+ 69 - 0
core/src/test/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequestTests.java

@@ -0,0 +1,69 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.action.admin.indices.template.put;
+
+import org.elasticsearch.Version;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+
+public class PutIndexTemplateRequestTests extends ESTestCase {
+
+    // bwc for #21009
+    public void testPutIndexTemplateRequest510() throws IOException {
+        PutIndexTemplateRequest putRequest = new PutIndexTemplateRequest("test");
+        putRequest.patterns(Collections.singletonList("test*"));
+        putRequest.order(5);
+
+        PutIndexTemplateRequest multiPatternRequest = new PutIndexTemplateRequest("test");
+        multiPatternRequest.patterns(Arrays.asList("test*", "*test2", "*test3*"));
+        multiPatternRequest.order(5);
+
+        // These bytes were retrieved by Base64 encoding the result of the above with 5_0_0 code.
+        // Note: Instead of a list for the template, in 5_0_0 the element was provided as a string.
+        String putRequestBytes = "ADwDAAR0ZXN0BXRlc3QqAAAABQAAAAAAAA==";
+        BytesArray bytes = new BytesArray(Base64.getDecoder().decode(putRequestBytes));
+
+        try (StreamInput in = bytes.streamInput()) {
+            in.setVersion(Version.V_5_0_0);
+            PutIndexTemplateRequest readRequest = new PutIndexTemplateRequest();
+            readRequest.readFrom(in);
+            assertEquals(putRequest.patterns(), readRequest.patterns());
+            assertEquals(putRequest.order(), readRequest.order());
+
+            BytesStreamOutput output = new BytesStreamOutput();
+            output.setVersion(Version.V_5_0_0);
+            readRequest.writeTo(output);
+            assertEquals(bytes.toBytesRef(), output.bytes().toBytesRef());
+
+            // test that multi templates are reverse-compatible.
+            // for the bwc case, if multiple patterns, use only the first pattern seen.
+            output.reset();
+            multiPatternRequest.writeTo(output);
+            assertEquals(bytes.toBytesRef(), output.bytes().toBytesRef());
+        }
+    }
+
+}

+ 2 - 1
core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java

@@ -46,6 +46,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.SortedSet;
@@ -224,7 +225,7 @@ public class RestoreBackwardsCompatIT extends AbstractSnapshotIntegTestCase {
         logger.info("--> check templates");
         IndexTemplateMetaData template = clusterState.getMetaData().templates().get("template_" + version.toLowerCase(Locale.ROOT));
         assertThat(template, notNullValue());
-        assertThat(template.template(), equalTo("te*"));
+        assertThat(template.patterns(), equalTo(Collections.singletonList("te*")));
         assertThat(template.settings().getAsInt(IndexMetaData.SETTING_NUMBER_OF_SHARDS, -1), equalTo(1));
         assertThat(template.mappings().size(), equalTo(1));
         assertThat(template.mappings().get("type1").string(), equalTo("{\"type1\":{\"_source\":{\"enabled\":false}}}"));

+ 1 - 1
core/src/test/java/org/elasticsearch/cluster/ClusterStateDiffIT.java

@@ -553,7 +553,7 @@ public class ClusterStateDiffIT extends ESIntegTestCase {
             public IndexTemplateMetaData randomCreate(String name) {
                 IndexTemplateMetaData.Builder builder = IndexTemplateMetaData.builder(name);
                 builder.order(randomInt(1000))
-                        .template(randomName("temp"))
+                        .patterns(Collections.singletonList(randomName("temp")))
                         .settings(randomSettings(Settings.EMPTY));
                 int aliasCount = randomIntBetween(0, 10);
                 for (int i = 0; i < aliasCount; i++) {

+ 4 - 2
core/src/test/java/org/elasticsearch/cluster/SimpleClusterStateIT.java

@@ -40,6 +40,8 @@ import org.elasticsearch.test.ESIntegTestCase;
 import org.elasticsearch.test.hamcrest.CollectionAssertions;
 import org.junit.Before;
 
+import java.util.Collections;
+
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertIndexTemplateExists;
 import static org.hamcrest.Matchers.equalTo;
@@ -92,7 +94,7 @@ public class SimpleClusterStateIT extends ESIntegTestCase {
 
     public void testIndexTemplates() throws Exception {
         client().admin().indices().preparePutTemplate("foo_template")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(0)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field1").field("type", "text").field("store", true).endObject()
@@ -101,7 +103,7 @@ public class SimpleClusterStateIT extends ESIntegTestCase {
                 .get();
 
         client().admin().indices().preparePutTemplate("fuu_template")
-                .setTemplate("test*")
+                .setPatterns(Collections.singletonList("test*"))
                 .setOrder(1)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field2").field("type", "text").field("store", "no").endObject()

+ 0 - 1
core/src/test/java/org/elasticsearch/cluster/metadata/IndexMetaDataTests.java

@@ -20,7 +20,6 @@
 package org.elasticsearch.cluster.metadata;
 
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
-import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.set.Sets;
 import org.elasticsearch.common.xcontent.ToXContent;

+ 81 - 0
core/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateMetaDataTests.java

@@ -0,0 +1,81 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.cluster.metadata;
+
+import org.elasticsearch.Version;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+
+import static org.elasticsearch.cluster.metadata.AliasMetaData.newAliasMetaDataBuilder;
+
+public class IndexTemplateMetaDataTests extends ESTestCase {
+
+    // bwc for #21009
+    public void testIndexTemplateMetaData510() throws IOException {
+        IndexTemplateMetaData metaData = IndexTemplateMetaData.builder("foo")
+            .patterns(Collections.singletonList("bar"))
+            .order(1)
+            .settings(Settings.builder()
+                .put("setting1", "value1")
+                .put("setting2", "value2"))
+            .putAlias(newAliasMetaDataBuilder("alias-bar1")).build();
+
+        IndexTemplateMetaData multiMetaData = IndexTemplateMetaData.builder("foo")
+            .patterns(Arrays.asList("bar", "foo"))
+            .order(1)
+            .settings(Settings.builder()
+                .put("setting1", "value1")
+                .put("setting2", "value2"))
+            .putAlias(newAliasMetaDataBuilder("alias-bar1")).build();
+
+        // These bytes were retrieved by Base64 encoding the result of the above with 5_0_0 code
+        String templateBytes = "A2ZvbwAAAAEDYmFyAghzZXR0aW5nMQEGdmFsdWUxCHNldHRpbmcyAQZ2YWx1ZTIAAQphbGlhcy1iYXIxAAAAAAA=";
+        BytesArray bytes = new BytesArray(Base64.getDecoder().decode(templateBytes));
+
+        try (StreamInput in = bytes.streamInput()) {
+            in.setVersion(Version.V_5_0_0);
+            IndexTemplateMetaData readMetaData = IndexTemplateMetaData.Builder.readFrom(in);
+            assertEquals(0, in.available());
+            assertEquals(metaData.getName(), readMetaData.getName());
+            assertEquals(metaData.getPatterns(), readMetaData.getPatterns());
+            assertTrue(metaData.aliases().containsKey("alias-bar1"));
+            assertEquals(1, metaData.aliases().size());
+
+            BytesStreamOutput output = new BytesStreamOutput();
+            output.setVersion(Version.V_5_0_0);
+            readMetaData.writeTo(output);
+            assertEquals(bytes.toBytesRef(), output.bytes().toBytesRef());
+
+            // test that multi templates are reverse-compatible.
+            // for the bwc case, if multiple patterns, use only the first pattern seen.
+            output.reset();
+            multiMetaData.writeTo(output);
+            assertEquals(bytes.toBytesRef(), output.bytes().toBytesRef());
+        }
+    }
+
+}

+ 5 - 3
core/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetaDataTests.java

@@ -26,6 +26,7 @@ import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.test.ESTestCase;
 
 import java.io.IOException;
+import java.util.Collections;
 
 import static org.elasticsearch.cluster.metadata.AliasMetaData.newAliasMetaDataBuilder;
 import static org.hamcrest.Matchers.equalTo;
@@ -33,6 +34,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
 
 public class ToAndFromJsonMetaDataTests extends ESTestCase {
+
     public void testSimpleJsonFromAndTo() throws IOException {
         MetaData metaData = MetaData.builder()
                 .put(IndexMetaData.builder("test1")
@@ -113,7 +115,7 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase {
                         .putAlias(newAliasMetaDataBuilder("alias2"))
                         .putAlias(newAliasMetaDataBuilder("alias4").filter(ALIAS_FILTER2)))
                 .put(IndexTemplateMetaData.builder("foo")
-                        .template("bar")
+                        .patterns(Collections.singletonList("bar"))
                         .order(1)
                         .settings(Settings.builder()
                                 .put("setting1", "value1")
@@ -134,7 +136,7 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase {
                         .putAlias(newAliasMetaDataBuilder("alias2"))
                         .putAlias(newAliasMetaDataBuilder("alias4").filter(ALIAS_FILTER2)))
                 .put(IndexTemplateMetaData.builder("foo")
-                        .template("bar")
+                        .patterns(Collections.singletonList("bar"))
                         .order(1)
                         .settings(Settings.builder()
                                 .put("setting1", "value1")
@@ -292,7 +294,7 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase {
 
         // templates
         assertThat(parsedMetaData.templates().get("foo").name(), is("foo"));
-        assertThat(parsedMetaData.templates().get("foo").template(), is("bar"));
+        assertThat(parsedMetaData.templates().get("foo").patterns(), is(Collections.singletonList("bar")));
         assertThat(parsedMetaData.templates().get("foo").settings().get("index.setting1"), is("value1"));
         assertThat(parsedMetaData.templates().get("foo").settings().getByPrefix("index.").get("setting2"), is("value2"));
         assertThat(parsedMetaData.templates().get("foo").aliases().size(), equalTo(3));

+ 3 - 2
core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java

@@ -53,6 +53,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.stream.IntStream;
@@ -356,7 +357,7 @@ public class RecoveryFromGatewayIT extends ESIntegTestCase {
                         .setSource(jsonBuilder().startObject().startObject("type2").endObject().endObject())
                         .execute().actionGet();
                     client.admin().indices().preparePutTemplate("template_1")
-                        .setTemplate("te*")
+                        .setPatterns(Collections.singletonList("te*"))
                         .setOrder(0)
                         .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                             .startObject("field1").field("type", "text").field("store", true).endObject()
@@ -383,7 +384,7 @@ public class RecoveryFromGatewayIT extends ESIntegTestCase {
 
         ClusterState state = client().admin().cluster().prepareState().execute().actionGet().getState();
         assertThat(state.metaData().index("test").mapping("type2"), notNullValue());
-        assertThat(state.metaData().templates().get("template_1").template(), equalTo("te*"));
+        assertThat(state.metaData().templates().get("template_1").patterns(), equalTo(Collections.singletonList("te*")));
         assertThat(state.metaData().index("test").getAliases().get("test_alias"), notNullValue());
         assertThat(state.metaData().index("test").getAliases().get("test_alias").filter(), notNullValue());
     }

+ 2 - 1
core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java

@@ -29,6 +29,7 @@ import org.elasticsearch.indices.TypeMissingException;
 import org.elasticsearch.test.ESIntegTestCase;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicReference;
@@ -129,7 +130,7 @@ public class DynamicMappingIT extends ESIntegTestCase {
     public void testAutoCreateWithDisabledDynamicMappings() throws Exception {
         assertAcked(client().admin().indices().preparePutTemplate("my_template")
             .setCreate(true)
-            .setTemplate("index_*")
+            .setPatterns(Collections.singletonList("index_*"))
             .addMapping("foo", "field", "type=keyword")
             .setSettings(Settings.builder().put("index.mapper.dynamic", false).build())
             .get());

+ 3 - 2
core/src/test/java/org/elasticsearch/indices/template/IndexTemplateBlocksIT.java

@@ -26,6 +26,7 @@ import org.elasticsearch.test.ESIntegTestCase;
 import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
 
 import java.io.IOException;
+import java.util.Collections;
 
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBlocked;
 import static org.hamcrest.Matchers.hasSize;
@@ -35,7 +36,7 @@ public class IndexTemplateBlocksIT extends ESIntegTestCase {
     public void testIndexTemplatesWithBlocks() throws IOException {
         // creates a simple index template
         client().admin().indices().preparePutTemplate("template_blocks")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(0)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field1").field("type", "text").field("store", true).endObject()
@@ -50,7 +51,7 @@ public class IndexTemplateBlocksIT extends ESIntegTestCase {
             assertThat(response.getIndexTemplates(), hasSize(1));
 
             assertBlocked(client().admin().indices().preparePutTemplate("template_blocks_2")
-                    .setTemplate("block*")
+                    .setPatterns(Collections.singletonList("block*"))
                     .setOrder(0)
                     .addAlias(new Alias("alias_1")));
 

+ 74 - 30
core/src/test/java/org/elasticsearch/indices/template/SimpleIndexTemplateIT.java

@@ -41,8 +41,10 @@ import org.elasticsearch.test.ESIntegTestCase;
 
 import org.junit.After;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -80,7 +82,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
 
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setSettings(indexSettings())
                 .setOrder(0)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
@@ -90,7 +92,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
                 .get();
 
         client().admin().indices().preparePutTemplate("template_2")
-                .setTemplate("test*")
+                .setPatterns(Collections.singletonList("test*"))
                 .setSettings(indexSettings())
                 .setOrder(1)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
@@ -100,7 +102,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
         // test create param
         assertThrows(client().admin().indices().preparePutTemplate("template_2")
-                .setTemplate("test*")
+                .setPatterns(Collections.singletonList("test*"))
                 .setSettings(indexSettings())
                 .setCreate(true)
                 .setOrder(1)
@@ -152,7 +154,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
         final int existingTemplates = admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size();
         logger.info("--> put template_1 and template_2");
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(0)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field1").field("type", "text").field("store", true).endObject()
@@ -161,7 +163,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
                 .execute().actionGet();
 
         client().admin().indices().preparePutTemplate("template_2")
-                .setTemplate("test*")
+                .setPatterns(Collections.singletonList("test*"))
                 .setOrder(1)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field2").field("type", "text").field("store", "no").endObject()
@@ -180,7 +182,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
         logger.info("--> put template_1 back");
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(0)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field1").field("type", "text").field("store", true).endObject()
@@ -202,7 +204,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
     public void testThatGetIndexTemplatesWorks() throws Exception {
         logger.info("--> put template_1");
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(0)
                 .setVersion(123)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
@@ -215,7 +217,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
         GetIndexTemplatesResponse getTemplate1Response = client().admin().indices().prepareGetTemplates("template_1").execute().actionGet();
         assertThat(getTemplate1Response.getIndexTemplates(), hasSize(1));
         assertThat(getTemplate1Response.getIndexTemplates().get(0), is(notNullValue()));
-        assertThat(getTemplate1Response.getIndexTemplates().get(0).getTemplate(), is("te*"));
+        assertThat(getTemplate1Response.getIndexTemplates().get(0).patterns(), is(Collections.singletonList("te*")));
         assertThat(getTemplate1Response.getIndexTemplates().get(0).getOrder(), is(0));
         assertThat(getTemplate1Response.getIndexTemplates().get(0).getVersion(), is(123));
 
@@ -228,7 +230,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
     public void testThatGetIndexTemplatesWithSimpleRegexWorks() throws Exception {
         logger.info("--> put template_1");
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(0)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field1").field("type", "text").field("store", true).endObject()
@@ -238,7 +240,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
         logger.info("--> put template_2");
         client().admin().indices().preparePutTemplate("template_2")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(0)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field1").field("type", "text").field("store", true).endObject()
@@ -248,7 +250,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
         logger.info("--> put template3");
         client().admin().indices().preparePutTemplate("template3")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(0)
                 .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                         .startObject("field1").field("type", "text").field("store", true).endObject()
@@ -316,7 +318,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
         MapperParsingException e = expectThrows( MapperParsingException.class,
             () -> client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .addMapping("type1", "abcde")
                 .get());
         assertThat(e.getMessage(), containsString("Failed to parse mapping "));
@@ -335,7 +337,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
         IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
             () -> client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setSettings(Settings.builder().put("does_not_exist", "test"))
                 .get());
         assertEquals("unknown setting [index.does_not_exist] please check that any required plugins are" +
@@ -353,7 +355,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
     public void testIndexTemplateWithAliases() throws Exception {
 
         client().admin().indices().preparePutTemplate("template_with_aliases")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .addMapping("type1", "{\"type1\" : {\"properties\" : {\"value\" : {\"type\" : \"text\"}}}}")
                 .addAlias(new Alias("simple_alias"))
                 .addAlias(new Alias("templated_alias-{index}"))
@@ -440,7 +442,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
     public void testIndexTemplateWithAliasesSource() {
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setAliases(
                         "    {\n" +
                         "        \"alias1\" : {},\n" +
@@ -478,7 +480,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
     public void testDuplicateAlias() throws Exception {
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .addAlias(new Alias("my_alias").filter(termQuery("field", "value1")))
                 .addAlias(new Alias("my_alias").filter(termQuery("field", "value2")))
                 .get();
@@ -492,7 +494,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
     public void testAliasInvalidFilterValidJson() throws Exception {
         //invalid filter but valid json: put index template works fine, fails during index creation
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .addAlias(new Alias("invalid_alias").filter("{ \"invalid\": {} }")).get();
 
         GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("template_1").get();
@@ -510,7 +512,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
     public void testAliasInvalidFilterInvalidJson() throws Exception {
         //invalid json: put index template fails
         PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .addAlias(new Alias("invalid_alias").filter("abcde"));
 
         IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
@@ -525,7 +527,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
         createIndex("index");
 
         client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .addAlias(new Alias("index")).get();
 
         InvalidAliasNameException e = expectThrows(InvalidAliasNameException.class,
@@ -535,7 +537,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
     public void testAliasEmptyName() throws Exception {
         PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .addAlias(new Alias("  ").indexRouting("1,2,3"));
 
         IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
@@ -545,7 +547,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
     public void testAliasWithMultipleIndexRoutings() throws Exception {
         PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .addAlias(new Alias("alias").indexRouting("1,2,3"));
 
         IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
@@ -555,7 +557,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
 
     public void testMultipleAliasesPrecedence() throws Exception {
         client().admin().indices().preparePutTemplate("template1")
-                .setTemplate("*")
+                .setPatterns(Collections.singletonList("*"))
                 .setOrder(0)
                 .addAlias(new Alias("alias1"))
                 .addAlias(new Alias("{index}-alias"))
@@ -563,7 +565,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
                 .addAlias(new Alias("alias4")).get();
 
         client().admin().indices().preparePutTemplate("template2")
-                .setTemplate("te*")
+                .setPatterns(Collections.singletonList("te*"))
                 .setOrder(1)
                 .addAlias(new Alias("alias1").routing("test"))
                 .addAlias(new Alias("alias3")).get();
@@ -593,27 +595,27 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
     public void testStrictAliasParsingInIndicesCreatedViaTemplates() throws Exception {
         // Indexing into a should succeed, because the field mapping for field 'field' is defined in the test mapping.
         client().admin().indices().preparePutTemplate("template1")
-                .setTemplate("a*")
+                .setPatterns(Collections.singletonList("a*"))
                 .setOrder(0)
                 .addMapping("test", "field", "type=text")
                 .addAlias(new Alias("alias1").filter(termQuery("field", "value"))).get();
         // Indexing into b should succeed, because the field mapping for field 'field' is defined in the _default_ mapping and
         //  the test type exists.
         client().admin().indices().preparePutTemplate("template2")
-                .setTemplate("b*")
+                .setPatterns(Collections.singletonList("b*"))
                 .setOrder(0)
                 .addMapping("_default_", "field", "type=text")
                 .addMapping("test")
                 .addAlias(new Alias("alias2").filter(termQuery("field", "value"))).get();
         // Indexing into c should succeed, because the field mapping for field 'field' is defined in the _default_ mapping.
         client().admin().indices().preparePutTemplate("template3")
-                .setTemplate("c*")
+                .setPatterns(Collections.singletonList("c*"))
                 .setOrder(0)
                 .addMapping("_default_", "field", "type=text")
                 .addAlias(new Alias("alias3").filter(termQuery("field", "value"))).get();
         // Indexing into d index should fail, since there is field with name 'field' in the mapping
         client().admin().indices().preparePutTemplate("template4")
-                .setTemplate("d*")
+                .setPatterns(Collections.singletonList("d*"))
                 .setOrder(0)
                 .addAlias(new Alias("alias4").filter(termQuery("field", "value"))).get();
 
@@ -672,7 +674,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
         //Now, a complete mapping with two separated templates is error
         // base template
         client().admin().indices().preparePutTemplate("template_1")
-            .setTemplate("*")
+            .setPatterns(Collections.singletonList("*"))
             .setSettings(
                 "    {\n" +
                     "        \"index\" : {\n" +
@@ -690,7 +692,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
         // put template using custom_1 analyzer
         MapperParsingException e = expectThrows(MapperParsingException.class,
             () -> client().admin().indices().preparePutTemplate("template_2")
-                    .setTemplate("test*")
+                    .setPatterns(Collections.singletonList("test*"))
                     .setCreate(true)
                     .setOrder(1)
                     .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
@@ -709,7 +711,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
         Integer version = randomBoolean() ? randomInt() : null;
 
         assertAcked(client().admin().indices().preparePutTemplate("versioned_template")
-                                              .setTemplate("te*")
+                                              .setPatterns(Collections.singletonList("te*"))
                                               .setVersion(version)
                                               .setOrder(order)
                                               .addMapping("test", "field", "type=text")
@@ -721,4 +723,46 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
         assertThat(response.getIndexTemplates().get(0).getOrder(), equalTo(order));
     }
 
+    public void testMultipleTemplate() throws IOException {
+        client().admin().indices().preparePutTemplate("template_1")
+            .setPatterns(Arrays.asList("a*", "b*"))
+            .setSettings(indexSettings())
+            .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
+            .startObject("field1").field("type", "text").field("store", true).endObject()
+            .startObject("field2").field("type", "keyword").field("store", false).endObject()
+            .endObject().endObject().endObject())
+            .get();
+
+        client().prepareIndex("ax", "type1", "1")
+            .setSource("field1", "value1", "field2", "value2")
+            .setRefreshPolicy(IMMEDIATE).get();
+
+        client().prepareIndex("bx", "type1", "1")
+            .setSource("field1", "value1", "field2", "value2")
+            .setRefreshPolicy(IMMEDIATE).get();
+
+        ensureGreen();
+
+        // ax -> matches template
+        SearchResponse searchResponse = client().prepareSearch("ax")
+            .setQuery(termQuery("field1", "value1"))
+            .addStoredField("field1")
+            .addStoredField("field2")
+            .execute().actionGet();
+
+        assertHitCount(searchResponse, 1);
+        assertEquals("value1", searchResponse.getHits().getAt(0).field("field1").value().toString());
+        assertNull(searchResponse.getHits().getAt(0).field("field2"));
+
+        // bx -> matches template
+        searchResponse = client().prepareSearch("bx")
+            .setQuery(termQuery("field1", "value1"))
+            .addStoredField("field1")
+            .addStoredField("field2")
+            .execute().actionGet();
+
+        assertHitCount(searchResponse, 1);
+        assertEquals("value1", searchResponse.getHits().getAt(0).field("field1").value().toString());
+        assertNull(searchResponse.getHits().getAt(0).field("field2"));
+    }
 }

+ 2 - 2
core/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java

@@ -445,7 +445,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
                 .setType("fs").setSettings(Settings.builder().put("location", randomRepoPath())));
 
         logger.info("-->  creating test template");
-        assertThat(client.admin().indices().preparePutTemplate("test-template").setTemplate("te*").addMapping("test-mapping", XContentFactory.jsonBuilder().startObject().startObject("test-mapping").startObject("properties")
+        assertThat(client.admin().indices().preparePutTemplate("test-template").setPatterns(Collections.singletonList("te*")).addMapping("test-mapping", XContentFactory.jsonBuilder().startObject().startObject("test-mapping").startObject("properties")
             .startObject("field1").field("type", "string").field("store", "yes").endObject()
             .startObject("field2").field("type", "string").field("store", "yes").field("index", "not_analyzed").endObject()
             .endObject().endObject().endObject()).get().isAcknowledged(), equalTo(true));
@@ -486,7 +486,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
 
         if(testTemplate) {
             logger.info("-->  creating test template");
-            assertThat(client.admin().indices().preparePutTemplate("test-template").setTemplate("te*").addMapping("test-mapping", XContentFactory.jsonBuilder().startObject().startObject("test-mapping").startObject("properties")
+            assertThat(client.admin().indices().preparePutTemplate("test-template").setPatterns(Collections.singletonList("te*")).addMapping("test-mapping", XContentFactory.jsonBuilder().startObject().startObject("test-mapping").startObject("properties")
                 .startObject("field1").field("type", "string").field("store", "yes").endObject()
                 .startObject("field2").field("type", "string").field("store", "yes").field("index", "not_analyzed").endObject()
                 .endObject().endObject().endObject()).get().isAcknowledged(), equalTo(true));

+ 5 - 5
docs/reference/cat.asciidoc

@@ -191,7 +191,7 @@ order by column3.
 
 [source,sh]
 --------------------------------------------------
-GET _cat/templates?v&s=order:desc,template
+GET _cat/templates?v&s=order:desc,index_patterns
 --------------------------------------------------
 //CONSOLE
 
@@ -199,10 +199,10 @@ returns:
 
 [source,sh]
 --------------------------------------------------
-name                  template     order version
-pizza_pepperoni       *pepperoni*  2
-sushi_california_roll *avocado*    1     1
-pizza_hawaiian        *pineapples* 1
+name                  index_patterns order version
+pizza_pepperoni       [*pepperoni*]  2
+sushi_california_roll [*avocado*]    1     1
+pizza_hawaiian        [*pineapples*] 1
 --------------------------------------------------
 
 --

+ 7 - 7
docs/reference/indices/templates.asciidoc

@@ -3,8 +3,8 @@
 
 Index templates allow you to define templates that will automatically be
 applied when new indices are created. The templates include both settings and
-mappings, and a simple pattern template that controls whether the template
-should be applied to the new index.
+mappings, and a list of patterns that control whether the template should be
+applied to the new index.
 
 NOTE: Templates are only applied at index creation time.  Changing a template
 will have no impact on existing indices.
@@ -15,7 +15,7 @@ For example:
 --------------------------------------------------
 PUT _template/template_1
 {
-  "template": "te*",
+  "index_patterns": ["te*", "bar*"],
   "settings": {
     "number_of_shards": 1
   },
@@ -53,7 +53,7 @@ It is also possible to include aliases in an index template as follows:
 --------------------------------------------------
 PUT _template/template_1
 {
-    "template" : "te*",
+    "index_patterns" : ["te*"],
     "settings" : {
         "number_of_shards" : 1
     },
@@ -147,7 +147,7 @@ orders overriding them. For example:
 --------------------------------------------------
 PUT /_template/template_1
 {
-    "template" : "*",
+    "index_patterns" : ["*"],
     "order" : 0,
     "settings" : {
         "number_of_shards" : 1
@@ -161,7 +161,7 @@ PUT /_template/template_1
 
 PUT /_template/template_2
 {
-    "template" : "te*",
+    "index_patterns" : ["te*"],
     "order" : 1,
     "settings" : {
         "number_of_shards" : 1
@@ -196,7 +196,7 @@ one.
 --------------------------------------------------
 PUT /_template/template_1
 {
-    "template" : "*",
+    "index_patterns" : ["*"],
     "order" : 0,
     "settings" : {
         "number_of_shards" : 1

+ 1 - 1
docs/reference/mapping/dynamic-mapping.asciidoc

@@ -61,7 +61,7 @@ Automatic type creation can also be disabled for all indices by setting an index
 --------------------------------------------------
 PUT _template/template_all
 {
-  "template": "*",
+  "index_patterns": ["*"],
   "order":0,
   "settings": {
     "index.mapper.dynamic": false <1>

+ 1 - 1
docs/reference/mapping/dynamic/default-mapping.asciidoc

@@ -44,7 +44,7 @@ within automatically created indices:
 --------------------------------------------------
 PUT _template/logging
 {
-  "template":   "logs-*", <1>
+  "index_patterns":   ["logs-*"], <1>
   "settings": { "number_of_shards": 1 }, <2>
   "mappings": {
     "_default_": {

+ 1 - 1
docs/reference/mapping/dynamic/templates.asciidoc

@@ -407,7 +407,7 @@ new indices, you could create the following index template:
 PUT _template/disable_all_field
 {
   "order": 0,
-  "template": "*", <1>
+  "index_patterns": ["*"], <1>
   "mappings": {
     "_default_": { <2>
       "_all": { <3>

+ 58 - 27
rest-api-spec/src/main/resources/rest-api-spec/test/cat.templates/10_basic.yaml

@@ -10,7 +10,7 @@
     - match:
         $body: |
                    /^  name             .+   \n
-                       template         .+   \n
+                       index_patterns   .+   \n
                        order            .+   \n
                        version          .+   \n
                    $/
@@ -39,7 +39,7 @@
           body:
             order: 0
             version: 1
-            template: test-*
+            index_patterns: test-*
             settings:
               number_of_shards: 1
               number_of_replicas: 0
@@ -50,7 +50,7 @@
           body:
             order: 1
             version: 2
-            template: test-2*
+            index_patterns: test-2*
             settings:
               number_of_shards: 1
               number_of_replicas: 0
@@ -61,7 +61,7 @@
     - match:
         $body: /
                  (^|\n)test     \s+
-                 test-\*        \s+
+                 \[test-\*\]    \s+
                  0              \s+
                  1
                  (\n|$)
@@ -70,7 +70,7 @@
     - match:
         $body: /
                 (^|\n)test_2    \s+
-                test-2\*        \s+
+                \[test-2\*\]    \s+
                 1               \s+
                 2
                 (\n|$)
@@ -87,7 +87,7 @@
           body:
             order: 0
             version: 1
-            template: t*
+            index_patterns: t*
             settings:
               number_of_shards: 1
               number_of_replicas: 0
@@ -98,7 +98,7 @@
           body:
             order: 2
             version: 1
-            template: tea*
+            index_patterns: tea*
             settings:
               number_of_shards: 1
               number_of_replicas: 0
@@ -111,7 +111,7 @@
         $body: |
                 /^
                     test    \s+
-                    t\*     \s+
+                    \[t\*\] \s+
                     0       \s+
                     1
                     \n
@@ -128,7 +128,7 @@
           body:
             order: 0
             version: 1
-            template: t*
+            index_patterns: t*
             settings:
               number_of_shards: 1
               number_of_replicas: 0
@@ -141,14 +141,14 @@
     - match:
         $body: |
                  /^
-                    name        \s+
-                    template    \s+
-                    order       \s+
+                    name            \s+
+                    index_patterns  \s+
+                    order           \s+
                     version
                     \n
-                    test        \s+
-                    t\*         \s+
-                    0           \s+
+                    test            \s+
+                    \[t\*\]         \s+
+                    0               \s+
                     1
                     \n
                  $/
@@ -164,14 +164,14 @@
           body:
             order: 0
             version: 1
-            template: t*
+            index_patterns: t*
             settings:
               number_of_shards: 1
               number_of_replicas: 0
 
     - do:
         cat.templates:
-            h:  [name, template]
+            h:  [name, index_patterns]
             v:  true
             name: test*
 
@@ -179,10 +179,10 @@
         $body: |
                 /^
                     name        \s+
-                    template
+                    index_patterns
                     \n
                     test        \s+
-                    t\*
+                    \[t\*\]
                     \n
                 $/
 
@@ -196,7 +196,7 @@
           name: test
           body:
             order: 0
-            template: t*
+            index_patterns: t*
             settings:
               number_of_shards: 1
               number_of_replicas: 0
@@ -207,31 +207,62 @@
           body:
             order: 0
             version: 1
-            template: te*
+            index_patterns: te*
             settings:
               number_of_shards: 1
               number_of_replicas: 0
 
     - do:
         cat.templates:
-            h: [name, template, version]
+            h: [name, index_patterns, version]
             s: [version]
 
     - match:
         $body: |
               /^
-                  test \s+ t\* \s+\n
-                  test_1 \s+ te\* \s+ 1\n
+                  test   \s+ \[t\*\]  \s+   \n
+                  test_1 \s+ \[te\*\] \s+ 1 \n
               $/
 
     - do:
         cat.templates:
-            h: [name, template, version]
+            h: [name, index_patterns, version]
             s: ["version:desc"]
 
     - match:
         $body: |
               /^
-                  test_1 \s+ te\* \s+ 1\n
-                  test \s+ t\* \s+\n
+                  test_1 \s+ \[te\*\]   \s+ 1\n
+                  test   \s+ \[t\*\]    \s+  \n
+
+              $/
+
+---
+"Multiple template":
+    - do:
+        indices.put_template:
+          name: test_1
+          body:
+            order: 0
+            version: 1
+            index_patterns: [t*, te*]
+            settings:
+              number_of_shards: 1
+              number_of_replicas: 0
+
+    - do:
+        cat.templates:
+            h: [name, index_patterns]
+            v: true
+
+
+    - match:
+       $body: |
+              /^
+                 name         \s+
+                 index_patterns
+                 \n
+                 test_1       \s+
+                 \[t\*,\ te\*\]
+                 \n
               $/

+ 1 - 1
rest-api-spec/src/main/resources/rest-api-spec/test/indices.exists_template/10_basic.yaml

@@ -16,7 +16,7 @@ setup:
       indices.put_template:
         name: test
         body:
-          template: 'test-*'
+          index_patterns: ['test-*']
           settings:
             number_of_shards:   1
             number_of_replicas: 0

+ 5 - 5
rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_template/10_basic.yaml

@@ -3,7 +3,7 @@ setup:
       indices.put_template:
         name: test
         body:
-          template: test-*
+          index_patterns: test-*
           settings:
             number_of_shards:   1
             number_of_replicas: 0
@@ -15,7 +15,7 @@ setup:
       indices.get_template:
         name: test
 
-  - match: {test.template: "test-*"}
+  - match: {test.index_patterns: ["test-*"]}
   - match: {test.settings: {index: {number_of_shards: '1', number_of_replicas: '0'}}}
 
 ---
@@ -25,15 +25,15 @@ setup:
       indices.put_template:
         name: test2
         body:
-          template: test2-*
+          index_patterns: test2-*
           settings:
             number_of_shards:   1
 
   - do:
       indices.get_template: {}
 
-  - match: {test.template: "test-*"}
-  - match: {test2.template: "test2-*"}
+  - match: {test.index_patterns: ["test-*"]}
+  - match: {test2.index_patterns: ["test2-*"]}
 
 ---
 "Get template with local flag":

+ 31 - 12
rest-api-spec/src/main/resources/rest-api-spec/test/indices.put_template/10_basic.yaml

@@ -4,7 +4,7 @@
       indices.put_template:
         name: test
         body:
-          template: test-*
+          index_patterns: test-*
           settings:
             number_of_shards:   1
             number_of_replicas: 0
@@ -14,7 +14,26 @@
         name: test
         flat_settings: true
 
-  - match: {test.template: "test-*"}
+  - match: {test.index_patterns: ["test-*"]}
+  - match: {test.settings: {index.number_of_shards: '1', index.number_of_replicas: '0'}}
+
+---
+"Put multiple template":
+  - do:
+      indices.put_template:
+        name: test
+        body:
+          index_patterns: [test-*, test2-*]
+          settings:
+            number_of_shards:   1
+            number_of_replicas: 0
+
+  - do:
+      indices.get_template:
+        name: test
+        flat_settings: true
+
+  - match: {test.index_patterns: ["test-*", "test2-*"]}
   - match: {test.settings: {index.number_of_shards: '1', index.number_of_replicas: '0'}}
 
 ---
@@ -23,7 +42,7 @@
       indices.put_template:
         name: test
         body:
-          template: test-*
+          index_patterns: test-*
           aliases:
             test_alias: {}
             test_blias: { routing: b }
@@ -33,7 +52,7 @@
       indices.get_template:
         name: test
 
-  - match:  { test.template: "test-*" }
+  - match:  { test.index_patterns: ["test-*"] }
   - length: { test.aliases: 3 }
   - is_true: test.aliases.test_alias
   - match: { test.aliases.test_blias.index_routing: "b" }
@@ -47,7 +66,7 @@
         name: test
         create: true
         body:
-          template: test-*
+          index_patterns: test-*
           settings:
             number_of_shards:   1
             number_of_replicas: 0
@@ -57,7 +76,7 @@
         name: test
         flat_settings: true
 
-  - match: {test.template: "test-*"}
+  - match: {test.index_patterns: ["test-*"]}
   - match: {test.settings: {index.number_of_shards: '1', index.number_of_replicas: '0'}}
 
   - do:
@@ -66,7 +85,7 @@
         name: test
         create: true
         body:
-          template: test-*
+          index_patterns: test-*
           settings:
             number_of_shards:   1
             number_of_replicas: 0
@@ -79,7 +98,7 @@
         body: >
           {
             "version": 10,
-            "template": "*",
+            "index_patterns": "*",
             "settings": { "number_of_shards": 1 }
           }
   - match: { acknowledged: true }
@@ -96,7 +115,7 @@
         body: >
           {
             "version": 9,
-            "template": "*",
+            "index_patterns": "*",
             "settings": { "number_of_shards": 1 }
           }
   - match: { acknowledged: true }
@@ -113,7 +132,7 @@
         body: >
           {
             "version": 6789,
-            "template": "*",
+            "index_patterns": "*",
             "settings": { "number_of_shards": 1 }
           }
   - match: { acknowledged: true }
@@ -129,7 +148,7 @@
         name: "my_template"
         body: >
           {
-            "template": "*",
+            "index_patterns": "*",
             "settings": { "number_of_shards": 1 }
           }
   - match: { acknowledged: true }
@@ -146,7 +165,7 @@
         body: >
           {
             "version": 5385,
-            "template": "*",
+            "index_patterns": "*",
             "settings": { "number_of_shards": 1 }
           }
   - match: { acknowledged: true }

+ 1 - 1
test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java

@@ -408,7 +408,7 @@ public abstract class ESIntegTestCase extends ESTestCase {
             }
             PutIndexTemplateRequestBuilder putTemplate = client().admin().indices()
                 .preparePutTemplate("random_index_template")
-                .setTemplate("*")
+                .setPatterns(Collections.singletonList("*"))
                 .setOrder(0)
                 .setSettings(randomSettingsBuilder);
             if (mappings != null) {

+ 1 - 1
test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java

@@ -87,7 +87,7 @@ public abstract class ESSingleNodeTestCase extends ESTestCase {
         assertFalse(clusterHealthResponse.isTimedOut());
         client().admin().indices()
             .preparePutTemplate("random_index_template")
-            .setTemplate("*")
+            .setPatterns(Collections.singletonList("*"))
             .setOrder(0)
             .setSettings(Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
             .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)).get();