Repository: olivere/elastic (go)

Issue:
Update or allow the `indices_put_template` to use the new `/_index_template` API endpoint
Please use the following questions as a guideline to help me answer
your issue/question without further inquiry. Thank you.

### Which version of Elastic are you using?

[ x ] elastic.v7 (for Elasticsearch 7.x)
[ ] elastic.v6 (for Elasticsearch 6.x)
[ ] elastic.v5 (for Elasticsearch 5.x)
[ ] elastic.v3 (for Elasticsearch 2.x)
[ ] elastic.v2 (for Elasticsearch 1.x)

### Please describe the expected behavior

I expect the `indices_put_template` to use the new https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-template.html#put-index-template-api-request-body `/_index_template/` API endpoint.

### Please describe the actual behavior

The library is routing my request to `/_template` which is creating 'legacy' index templates

### Any steps to reproduce the behavior?



Pull request description:
Add new index template APIs
This commit implements the new Index Template APIs as described in
https://www.elastic.co/guide/en/elasticsearch/reference/7.9/index-templates.html.

They replace the legacy/v1 APIs and were implemented in 7.8.

In general, use `client.IndexXXXIndexTemplate(...)` instead of
`client.IndexXXXTemplate(...)` (with `XXX` being `Get`, `Put`, or
`Delete`) to use the newer versions.

Notice that this commit only implements the new Index Template APIs, not
the Component Template APIs.

Close #1393

Patch:
diff --git a/client.go b/client.go
index b536269..e015b77 100644
--- a/client.go
+++ b/client.go
@@ -1742,30 +1742,82 @@ func (c *Client) Aliases() *AliasesService {
 	return NewAliasesService(c)
 }
 
-// IndexGetTemplate gets an index template.
-// Use XXXTemplate funcs to manage search templates.
+// -- Legacy templates --
+
+// IndexGetTemplate gets an index template (v1/legacy version before 7.8).
+//
+// This service implements the legacy version of index templates as described
+// in https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html.
+//
+// See e.g. IndexPutIndexTemplate and IndexPutComponentTemplate for the new version(s).
 func (c *Client) IndexGetTemplate(names ...string) *IndicesGetTemplateService {
 	return NewIndicesGetTemplateService(c).Name(names...)
 }
 
-// IndexTemplateExists gets check if an index template exists.
-// Use XXXTemplate funcs to manage search templates.
+// IndexTemplateExists gets check if an index template exists (v1/legacy version before 7.8).
+//
+// This service implements the legacy version of index templates as described
+// in https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html.
+//
+// See e.g. IndexPutIndexTemplate and IndexPutComponentTemplate for the new version(s).
 func (c *Client) IndexTemplateExists(name string) *IndicesExistsTemplateService {
 	return NewIndicesExistsTemplateService(c).Name(name)
 }
 
-// IndexPutTemplate creates or updates an index template.
-// Use XXXTemplate funcs to manage search templates.
+// IndexPutTemplate creates or updates an index template (v1/legacy version before 7.8).
+//
+// This service implements the legacy version of index templates as described
+// in https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html.
+//
+// See e.g. IndexPutIndexTemplate and IndexPutComponentTemplate for the new version(s).
 func (c *Client) IndexPutTemplate(name string) *IndicesPutTemplateService {
 	return NewIndicesPutTemplateService(c).Name(name)
 }
 
-// IndexDeleteTemplate deletes an index template.
-// Use XXXTemplate funcs to manage search templates.
+// IndexDeleteTemplate deletes an index template (v1/legacy version before 7.8).
+//
+// This service implements the legacy version of index templates as described
+// in https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html.
+//
+// See e.g. IndexPutIndexTemplate and IndexPutComponentTemplate for the new version(s).
 func (c *Client) IndexDeleteTemplate(name string) *IndicesDeleteTemplateService {
 	return NewIndicesDeleteTemplateService(c).Name(name)
 }
 
+// -- Index templates --
+
+// IndexPutIndexTemplate creates or updates an index template (new version after 7.8).
+//
+// This service implements the new version of index templates as described
+// on https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-put-template.html.
+//
+// See e.g. IndexPutTemplate for the v1/legacy version.
+func (c *Client) IndexPutIndexTemplate(name string) *IndicesPutIndexTemplateService {
+	return NewIndicesPutIndexTemplateService(c).Name(name)
+}
+
+// IndexGetIndexTemplate returns an index template (new version after 7.8).
+//
+// This service implements the new version of index templates as described
+// on https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-get-template.html.
+//
+// See e.g. IndexPutTemplate for the v1/legacy version.
+func (c *Client) IndexGetIndexTemplate(name string) *IndicesGetIndexTemplateService {
+	return NewIndicesGetIndexTemplateService(c).Name(name)
+}
+
+// IndexDeleteIndexTemplate deletes an index template (new version after 7.8).
+//
+// This service implements the new version of index templates as described
+// on https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-delete-template.html.
+//
+// See e.g. IndexPutTemplate for the v1/legacy version.
+func (c *Client) IndexDeleteIndexTemplate(name string) *IndicesDeleteIndexTemplateService {
+	return NewIndicesDeleteIndexTemplateService(c).Name(name)
+}
+
+// -- TODO Component templates --
+
 // GetMapping gets a mapping.
 func (c *Client) GetMapping() *IndicesGetMappingService {
 	return NewIndicesGetMappingService(c)
diff --git a/indices_delete_index_template.go b/indices_delete_index_template.go
new file mode 100644
index 0000000..1e7d22b
--- /dev/null
+++ b/indices_delete_index_template.go
@@ -0,0 +1,186 @@
+// Copyright 2012-present Oliver Eilhard. All rights reserved.
+// Use of this source code is governed by a MIT-license.
+// See http://olivere.mit-license.org/license.txt for details.
+
+package elastic
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"github.com/olivere/elastic/v7/uritemplates"
+)
+
+// IndicesDeleteIndexTemplateService deletes index templates.
+//
+// Index templates have changed during in 7.8 update of Elasticsearch.
+// This service implements the new version (7.8 or later). If you want
+// the old version, please use the IndicesDeleteTemplateService.
+//
+// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-delete-template.html
+// for more details.
+type IndicesDeleteIndexTemplateService struct {
+	client *Client
+
+	pretty     *bool       // pretty format the returned JSON response
+	human      *bool       // return human readable values for statistics
+	errorTrace *bool       // include the stack trace of returned errors
+	filterPath []string    // list of filters used to reduce the response
+	headers    http.Header // custom request-level HTTP headers
+
+	name          string
+	timeout       string
+	masterTimeout string
+}
+
+// NewIndicesDeleteIndexTemplateService creates a new IndicesDeleteIndexTemplateService.
+func NewIndicesDeleteIndexTemplateService(client *Client) *IndicesDeleteIndexTemplateService {
+	return &IndicesDeleteIndexTemplateService{
+		client: client,
+	}
+}
+
+// Pretty tells Elasticsearch whether to return a formatted JSON response.
+func (s *IndicesDeleteIndexTemplateService) Pretty(pretty bool) *IndicesDeleteIndexTemplateService {
+	s.pretty = &pretty
+	return s
+}
+
+// Human specifies whether human readable values should be returned in
+// the JSON response, e.g. "7.5mb".
+func (s *IndicesDeleteIndexTemplateService) Human(human bool) *IndicesDeleteIndexTemplateService {
+	s.human = &human
+	return s
+}
+
+// ErrorTrace specifies whether to include the stack trace of returned errors.
+func (s *IndicesDeleteIndexTemplateService) ErrorTrace(errorTrace bool) *IndicesDeleteIndexTemplateService {
+	s.errorTrace = &errorTrace
+	return s
+}
+
+// FilterPath specifies a list of filters used to reduce the response.
+func (s *IndicesDeleteIndexTemplateService) FilterPath(filterPath ...string) *IndicesDeleteIndexTemplateService {
+	s.filterPath = filterPath
+	return s
+}
+
+// Header adds a header to the request.
+func (s *IndicesDeleteIndexTemplateService) Header(name string, value string) *IndicesDeleteIndexTemplateService {
+	if s.headers == nil {
+		s.headers = http.Header{}
+	}
+	s.headers.Add(name, value)
+	return s
+}
+
+// Headers specifies the headers of the request.
+func (s *IndicesDeleteIndexTemplateService) Headers(headers http.Header) *IndicesDeleteIndexTemplateService {
+	s.headers = headers
+	return s
+}
+
+// Name is the name of the template.
+func (s *IndicesDeleteIndexTemplateService) Name(name string) *IndicesDeleteIndexTemplateService {
+	s.name = name
+	return s
+}
+
+// Timeout is an explicit operation timeout.
+func (s *IndicesDeleteIndexTemplateService) Timeout(timeout string) *IndicesDeleteIndexTemplateService {
+	s.timeout = timeout
+	return s
+}
+
+// MasterTimeout specifies the timeout for connection to master.
+func (s *IndicesDeleteIndexTemplateService) MasterTimeout(masterTimeout string) *IndicesDeleteIndexTemplateService {
+	s.masterTimeout = masterTimeout
+	return s
+}
+
+// buildURL builds the URL for the operation.
+func (s *IndicesDeleteIndexTemplateService) buildURL() (string, url.Values, error) {
+	// Build URL
+	path, err := uritemplates.Expand("/_index_template/{name}", map[string]string{
+		"name": s.name,
+	})
+	if err != nil {
+		return "", url.Values{}, err
+	}
+
+	// Add query string parameters
+	params := url.Values{}
+	if v := s.pretty; v != nil {
+		params.Set("pretty", fmt.Sprint(*v))
+	}
+	if v := s.human; v != nil {
+		params.Set("human", fmt.Sprint(*v))
+	}
+	if v := s.errorTrace; v != nil {
+		params.Set("error_trace", fmt.Sprint(*v))
+	}
+	if len(s.filterPath) > 0 {
+		params.Set("filter_path", strings.Join(s.filterPath, ","))
+	}
+	if s.timeout != "" {
+		params.Set("timeout", s.timeout)
+	}
+	if s.masterTimeout != "" {
+		params.Set("master_timeout", s.masterTimeout)
+	}
+	return path, params, nil
+}
+
+// Validate checks if the operation is valid.
+func (s *IndicesDeleteIndexTemplateService) Validate() error {
+	var invalid []string
+	if s.name == "" {
+		invalid = append(invalid, "Name")
+	}
+	if len(invalid) > 0 {
+		return fmt.Errorf("missing required fields: %v", invalid)
+	}
+	return nil
+}
+
+// Do executes the operation.
+func (s *IndicesDeleteIndexTemplateService) Do(ctx context.Context) (*IndicesDeleteIndexTemplateResponse, error) {
+	// Check pre-conditions
+	if err := s.Validate(); err != nil {
+		return nil, err
+	}
+
+	// Get URL for request
+	path, params, err := s.buildURL()
+	if err != nil {
+		return nil, err
+	}
+
+	// Get HTTP response
+	res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
+		Method:  "DELETE",
+		Path:    path,
+		Params:  params,
+		Headers: s.headers,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	// Return operation response
+	ret := new(IndicesDeleteIndexTemplateResponse)
+	if err := s.client.decoder.Decode(res.Body, ret); err != nil {
+		return nil, err
+	}
+	return ret, nil
+}
+
+// IndicesDeleteIndexTemplateResponse is the response of IndicesDeleteIndexTemplateService.Do.
+type IndicesDeleteIndexTemplateResponse struct {
+	Acknowledged       bool   `json:"acknowledged"`
+	ShardsAcknowledged bool   `json:"shards_acknowledged"`
+	Index              string `json:"index,omitempty"`
+}
diff --git a/indices_delete_template.go b/indices_delete_template.go
index e1ed3e6..7dd5506 100644
--- a/indices_delete_template.go
+++ b/indices_delete_template.go
@@ -14,8 +14,14 @@ import (
 	"github.com/olivere/elastic/v7/uritemplates"
 )
 
-// IndicesDeleteTemplateService deletes index templates.
-// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-templates.html.
+// IndicesDeleteTemplateService deletes templates.
+//
+// Index templates have changed during in 7.8 update of Elasticsearch.
+// This service implements the legacy version (7.7 or lower). If you want
+// the new version, please use the IndicesDeleteIndexTemplateService.
+//
+// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-delete-template-v1.html
+// for more details.
 type IndicesDeleteTemplateService struct {
 	client *Client
 
diff --git a/indices_exists_template.go b/indices_exists_template.go
index c5c9bbd..53f29fe 100644
--- a/indices_exists_template.go
+++ b/indices_exists_template.go
@@ -26,8 +26,9 @@ type IndicesExistsTemplateService struct {
 	filterPath []string    // list of filters used to reduce the response
 	headers    http.Header // custom request-level HTTP headers
 
-	name  string
-	local *bool
+	name          string
+	local         *bool
+	masterTimeout string
 }
 
 // NewIndicesExistsTemplateService creates a new IndicesExistsTemplateService.
@@ -90,6 +91,12 @@ func (s *IndicesExistsTemplateService) Local(local bool) *IndicesExistsTemplateS
 	return s
 }
 
+// MasterTimeout specifies the timeout for connection to master.
+func (s *IndicesExistsTemplateService) MasterTimeout(masterTimeout string) *IndicesExistsTemplateService {
+	s.masterTimeout = masterTimeout
+	return s
+}
+
 // buildURL builds the URL for the operation.
 func (s *IndicesExistsTemplateService) buildURL() (string, url.Values, error) {
 	// Build URL
@@ -115,7 +122,10 @@ func (s *IndicesExistsTemplateService) buildURL() (string, url.Values, error) {
 		params.Set("filter_path", strings.Join(s.filterPath, ","))
 	}
 	if s.local != nil {
-		params.Set("local", fmt.Sprintf("%v", *s.local))
+		params.Set("local", fmt.Sprint(*s.local))
+	}
+	if s.masterTimeout != "" {
+		params.Set("master_timeout", s.masterTimeout)
 	}
 	return path, params, nil
 }
diff --git a/indices_get_index_template.go b/indices_get_index_template.go
new file mode 100644
index 0000000..0c3baef
--- /dev/null
+++ b/indices_get_index_template.go
@@ -0,0 +1,214 @@
+// Copyright 2012-present Oliver Eilhard. All rights reserved.
+// Use of this source code is governed by a MIT-license.
+// See http://olivere.mit-license.org/license.txt for details.
+
+package elastic
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"github.com/olivere/elastic/v7/uritemplates"
+)
+
+// IndicesGetIndexTemplateService returns an index template.
+//
+// Index templates have changed during in 7.8 update of Elasticsearch.
+// This service implements the new version (7.8 or later). If you want
+// the old version, please use the IndicesGetTemplateService.
+//
+// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-get-template.html
+// for more details.
+type IndicesGetIndexTemplateService struct {
+	client *Client
+
+	pretty     *bool       // pretty format the returned JSON response
+	human      *bool       // return human readable values for statistics
+	errorTrace *bool       // include the stack trace of returned errors
+	filterPath []string    // list of filters used to reduce the response
+	headers    http.Header // custom request-level HTTP headers
+
+	name          []string
+	masterTimeout string
+	flatSettings  *bool
+	local         *bool
+}
+
+// NewIndicesGetIndexTemplateService creates a new IndicesGetIndexTemplateService.
+func NewIndicesGetIndexTemplateService(client *Client) *IndicesGetIndexTemplateService {
+	return &IndicesGetIndexTemplateService{
+		client: client,
+		name:   make([]string, 0),
+	}
+}
+
+// Pretty tells Elasticsearch whether to return a formatted JSON response.
+func (s *IndicesGetIndexTemplateService) Pretty(pretty bool) *IndicesGetIndexTemplateService {
+	s.pretty = &pretty
+	return s
+}
+
+// Human specifies whether human readable values should be returned in
+// the JSON response, e.g. "7.5mb".
+func (s *IndicesGetIndexTemplateService) Human(human bool) *IndicesGetIndexTemplateService {
+	s.human = &human
+	return s
+}
+
+// ErrorTrace specifies whether to include the stack trace of returned errors.
+func (s *IndicesGetIndexTemplateService) ErrorTrace(errorTrace bool) *IndicesGetIndexTemplateService {
+	s.errorTrace = &errorTrace
+	return s
+}
+
+// FilterPath specifies a list of filters used to reduce the response.
+func (s *IndicesGetIndexTemplateService) FilterPath(filterPath ...string) *IndicesGetIndexTemplateService {
+	s.filterPath = filterPath
+	return s
+}
+
+// Header adds a header to the request.
+func (s *IndicesGetIndexTemplateService) Header(name string, value string) *IndicesGetIndexTemplateService {
+	if s.headers == nil {
+		s.headers = http.Header{}
+	}
+	s.headers.Add(name, value)
+	return s
+}
+
+// Headers specifies the headers of the request.
+func (s *IndicesGetIndexTemplateService) Headers(headers http.Header) *IndicesGetIndexTemplateService {
+	s.headers = headers
+	return s
+}
+
+// Name is the name of the index template.
+func (s *IndicesGetIndexTemplateService) Name(name ...string) *IndicesGetIndexTemplateService {
+	s.name = append(s.name, name...)
+	return s
+}
+
+// FlatSettings is returns settings in flat format (default: false).
+func (s *IndicesGetIndexTemplateService) FlatSettings(flatSettings bool) *IndicesGetIndexTemplateService {
+	s.flatSettings = &flatSettings
+	return s
+}
+
+// Local indicates whether to return local information, i.e. do not retrieve
+// the state from master node (default: false).
+func (s *IndicesGetIndexTemplateService) Local(local bool) *IndicesGetIndexTemplateService {
+	s.local = &local
+	return s
+}
+
+// MasterTimeout specifies the timeout for connection to master.
+func (s *IndicesGetIndexTemplateService) MasterTimeout(masterTimeout string) *IndicesGetIndexTemplateService {
+	s.masterTimeout = masterTimeout
+	return s
+}
+
+// buildURL builds the URL for the operation.
+func (s *IndicesGetIndexTemplateService) buildURL() (string, url.Values, error) {
+	// Build URL
+	var err error
+	var path string
+	if len(s.name) > 0 {
+		path, err = uritemplates.Expand("/_index_template/{name}", map[string]string{
+			"name": strings.Join(s.name, ","),
+		})
+	} else {
+		path = "/_template"
+	}
+	if err != nil {
+		return "", url.Values{}, err
+	}
+
+	// Add query string parameters
+	params := url.Values{}
+	if v := s.pretty; v != nil {
+		params.Set("pretty", fmt.Sprint(*v))
+	}
+	if v := s.human; v != nil {
+		params.Set("human", fmt.Sprint(*v))
+	}
+	if v := s.errorTrace; v != nil {
+		params.Set("error_trace", fmt.Sprint(*v))
+	}
+	if len(s.filterPath) > 0 {
+		params.Set("filter_path", strings.Join(s.filterPath, ","))
+	}
+	if s.flatSettings != nil {
+		params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
+	}
+	if s.local != nil {
+		params.Set("local", fmt.Sprintf("%v", *s.local))
+	}
+	if s.masterTimeout != "" {
+		params.Set("master_timeout", s.masterTimeout)
+	}
+	return path, params, nil
+}
+
+// Validate checks if the operation is valid.
+func (s *IndicesGetIndexTemplateService) Validate() error {
+	return nil
+}
+
+// Do executes the operation.
+func (s *IndicesGetIndexTemplateService) Do(ctx context.Context) (*IndicesGetIndexTemplateResponse, error) {
+	// Check pre-conditions
+	if err := s.Validate(); err != nil {
+		return nil, err
+	}
+
+	// Get URL for request
+	path, params, err := s.buildURL()
+	if err != nil {
+		return nil, err
+	}
+
+	// Get HTTP response
+	res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
+		Method:  "GET",
+		Path:    path,
+		Params:  params,
+		Headers: s.headers,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	// Return operation response
+	var ret *IndicesGetIndexTemplateResponse
+	if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
+		return nil, err
+	}
+	return ret, nil
+}
+
+// IndicesGetIndexTemplateResponse is the response of IndicesGetIndexTemplateService.Do.
+type IndicesGetIndexTemplateResponse struct {
+	IndexTemplates []IndicesGetIndexTemplates `json:"index_templates"`
+}
+
+type IndicesGetIndexTemplates struct {
+	Name          string                   `json:"name"`
+	IndexTemplate *IndicesGetIndexTemplate `json:"index_template"`
+}
+
+type IndicesGetIndexTemplate struct {
+	IndexPatterns []string                     `json:"index_patterns,omitempty"`
+	ComposedOf    []string                     `json:"composed_of,omitempty"`
+	Priority      int                          `json:"priority,omitempty"`
+	Version       int                          `json:"version,omitempty"`
+	Template      *IndicesGetIndexTemplateData `json:"template,omitempty"`
+}
+
+type IndicesGetIndexTemplateData struct {
+	Settings map[string]interface{} `json:"settings,omitempty"`
+	Mappings map[string]interface{} `json:"mappings,omitempty"`
+	Aliases  map[string]interface{} `json:"aliases,omitempty"`
+}
diff --git a/indices_get_template.go b/indices_get_template.go
index aeafe91..28bb4a0 100644
--- a/indices_get_template.go
+++ b/indices_get_template.go
@@ -14,8 +14,14 @@ import (
 	"github.com/olivere/elastic/v7/uritemplates"
 )
 
-// IndicesGetTemplateService returns an index template.
-// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-templates.html.
+// IndicesGetTemplateService returns an index template (v1).
+//
+// Index templates have changed during in 7.8 update of Elasticsearch.
+// This service implements the legacy version (7.7 or lower). If you want
+// the new version, please use the IndicesGetIndexTemplateService.
+//
+// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-get-template-v1.html
+// for more details.
 type IndicesGetTemplateService struct {
 	client *Client
 
diff --git a/indices_put_index_template.go b/indices_put_index_template.go
new file mode 100644
index 0000000..fb77657
--- /dev/null
+++ b/indices_put_index_template.go
@@ -0,0 +1,226 @@
+// Copyright 2012-present Oliver Eilhard. All rights reserved.
+// Use of this source code is governed by a MIT-license.
+// See http://olivere.mit-license.org/license.txt for details.
+
+package elastic
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"github.com/olivere/elastic/v7/uritemplates"
+)
+
+// IndicesPutIndexTemplateService creates or updates index templates.
+//
+// Index templates have changed during in 7.8 update of Elasticsearch.
+// This service implements the new version (7.8 or higher) for managing
+// index templates. If you want the v1/legacy version, please see e.g.
+// IndicesPutTemplateService and friends.
+//
+// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-put-template.html
+// for more details on this API.
+type IndicesPutIndexTemplateService struct {
+	client *Client
+
+	pretty     *bool       // pretty format the returned JSON response
+	human      *bool       // return human readable values for statistics
+	errorTrace *bool       // include the stack trace of returned errors
+	filterPath []string    // list of filters used to reduce the response
+	headers    http.Header // custom request-level HTTP headers
+
+	name          string
+	create        *bool
+	cause         string
+	masterTimeout string
+
+	bodyJson   interface{}
+	bodyString string
+}
+
+// NewIndicesPutIndexTemplateService creates a new IndicesPutIndexTemplateService.
+func NewIndicesPutIndexTemplateService(client *Client) *IndicesPutIndexTemplateService {
+	return &IndicesPutIndexTemplateService{
+		client: client,
+	}
+}
+
+// Pretty tells Elasticsearch whether to return a formatted JSON response.
+func (s *IndicesPutIndexTemplateService) Pretty(pretty bool) *IndicesPutIndexTemplateService {
+	s.pretty = &pretty
+	return s
+}
+
+// Human specifies whether human readable values should be returned in
+// the JSON response, e.g. "7.5mb".
+func (s *IndicesPutIndexTemplateService) Human(human bool) *IndicesPutIndexTemplateService {
+	s.human = &human
+	return s
+}
+
+// ErrorTrace specifies whether to include the stack trace of returned errors.
+func (s *IndicesPutIndexTemplateService) ErrorTrace(errorTrace bool) *IndicesPutIndexTemplateService {
+	s.errorTrace = &errorTrace
+	return s
+}
+
+// FilterPath specifies a list of filters used to reduce the response.
+func (s *IndicesPutIndexTemplateService) FilterPath(filterPath ...string) *IndicesPutIndexTemplateService {
+	s.filterPath = filterPath
+	return s
+}
+
+// Header adds a header to the request.
+func (s *IndicesPutIndexTemplateService) Header(name string, value string) *IndicesPutIndexTemplateService {
+	if s.headers == nil {
+		s.headers = http.Header{}
+	}
+	s.headers.Add(name, value)
+	return s
+}
+
+// Headers specifies the headers of the request.
+func (s *IndicesPutIndexTemplateService) Headers(headers http.Header) *IndicesPutIndexTemplateService {
+	s.headers = headers
+	return s
+}
+
+// Name is the name of the index template.
+func (s *IndicesPutIndexTemplateService) Name(name string) *IndicesPutIndexTemplateService {
+	s.name = name
+	return s
+}
+
+// Create indicates whether the index template should only be added if
+// new or can also replace an existing one.
+func (s *IndicesPutIndexTemplateService) Create(create bool) *IndicesPutIndexTemplateService {
+	s.create = &create
+	return s
+}
+
+// Cause is the user-defined reason for creating/updating the the index template.
+func (s *IndicesPutIndexTemplateService) Cause(cause string) *IndicesPutIndexTemplateService {
+	s.cause = cause
+	return s
+}
+
+// MasterTimeout specifies the timeout for connection to master.
+func (s *IndicesPutIndexTemplateService) MasterTimeout(masterTimeout string) *IndicesPutIndexTemplateService {
+	s.masterTimeout = masterTimeout
+	return s
+}
+
+// BodyJson is the index template definition as a JSON serializable
+// type, e.g. map[string]interface{}.
+func (s *IndicesPutIndexTemplateService) BodyJson(body interface{}) *IndicesPutIndexTemplateService {
+	s.bodyJson = body
+	return s
+}
+
+// BodyString is the index template definition as a raw string.
+func (s *IndicesPutIndexTemplateService) BodyString(body string) *IndicesPutIndexTemplateService {
+	s.bodyString = body
+	return s
+}
+
+// buildURL builds the URL for the operation.
+func (s *IndicesPutIndexTemplateService) buildURL() (string, url.Values, error) {
+	// Build URL
+	path, err := uritemplates.Expand("/_index_template/{name}", map[string]string{
+		"name": s.name,
+	})
+	if err != nil {
+		return "", url.Values{}, err
+	}
+
+	// Add query string parameters
+	params := url.Values{}
+	if v := s.pretty; v != nil {
+		params.Set("pretty", fmt.Sprint(*v))
+	}
+	if v := s.human; v != nil {
+		params.Set("human", fmt.Sprint(*v))
+	}
+	if v := s.errorTrace; v != nil {
+		params.Set("error_trace", fmt.Sprint(*v))
+	}
+	if len(s.filterPath) > 0 {
+		params.Set("filter_path", strings.Join(s.filterPath, ","))
+	}
+	if s.create != nil {
+		params.Set("create", fmt.Sprint(*s.create))
+	}
+	if s.cause != "" {
+		params.Set("cause", s.cause)
+	}
+	if s.masterTimeout != "" {
+		params.Set("master_timeout", s.masterTimeout)
+	}
+	return path, params, nil
+}
+
+// Validate checks if the operation is valid.
+func (s *IndicesPutIndexTemplateService) Validate() error {
+	var invalid []string
+	if s.name == "" {
+		invalid = append(invalid, "Name")
+	}
+	if s.bodyString == "" && s.bodyJson == nil {
+		invalid = append(invalid, "BodyJson")
+	}
+	if len(invalid) > 0 {
+		return fmt.Errorf("missing required fields: %v", invalid)
+	}
+	return nil
+}
+
+// Do executes the operation.
+func (s *IndicesPutIndexTemplateService) Do(ctx context.Context) (*IndicesPutIndexTemplateResponse, error) {
+	// Check pre-conditions
+	if err := s.Validate(); err != nil {
+		return nil, err
+	}
+
+	// Get URL for request
+	path, params, err := s.buildURL()
+	if err != nil {
+		return nil, err
+	}
+
+	// Setup HTTP request body
+	var body interface{}
+	if s.bodyJson != nil {
+		body = s.bodyJson
+	} else {
+		body = s.bodyString
+	}
+
+	// Get HTTP response
+	res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
+		Method:  "PUT",
+		Path:    path,
+		Params:  params,
+		Body:    body,
+		Headers: s.headers,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	// Return operation response
+	ret := new(IndicesPutIndexTemplateResponse)
+	if err := s.client.decoder.Decode(res.Body, ret); err != nil {
+		return nil, err
+	}
+	return ret, nil
+}
+
+// IndicesPutIndexTemplateResponse is the response of IndicesPutIndexTemplateService.Do.
+type IndicesPutIndexTemplateResponse struct {
+	Acknowledged       bool   `json:"acknowledged"`
+	ShardsAcknowledged bool   `json:"shards_acknowledged"`
+	Index              string `json:"index,omitempty"`
+}
diff --git a/indices_put_template.go b/indices_put_template.go
index 4ced8e8..bafa81e 100644
--- a/indices_put_template.go
+++ b/indices_put_template.go
@@ -14,8 +14,14 @@ import (
 	"github.com/olivere/elastic/v7/uritemplates"
 )
 
-// IndicesPutTemplateService creates or updates index mappings.
-// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-templates.html.
+// IndicesPutTemplateService creates or updates templates.
+//
+// Index templates have changed during in 7.8 update of Elasticsearch.
+// This service implements the legacy version (7.7 or lower). If you want
+// the new version, please use the IndicesPutIndexTemplateService.
+//
+// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html
+// for more details.
 type IndicesPutTemplateService struct {
 	client *Client
 


Test patch:
diff --git a/indices_index_templates_test.go b/indices_index_templates_test.go
new file mode 100644
index 0000000..6f45444
--- /dev/null
+++ b/indices_index_templates_test.go
@@ -0,0 +1,69 @@
+// Copyright 2012-present Oliver Eilhard. All rights reserved.
+// Use of this source code is governed by a MIT-license.
+// See http://olivere.mit-license.org/license.txt for details.
+
+package elastic
+
+import (
+	"context"
+	"testing"
+)
+
+func TestIndexTemplatesLifecycle(t *testing.T) {
+	client := setupTestClientAndCreateIndex(t)
+
+	const templateName = "template_1"
+
+	// Always make sure the index template is deleted
+	defer func() {
+		_, _ = client.IndexDeleteIndexTemplate(templateName).Pretty(true).Do(context.Background())
+	}()
+
+	// Create an index template
+	{
+		resp, err := client.IndexPutIndexTemplate(templateName).Pretty(true).BodyString(`{
+			"index_patterns": ["elastic-index-templates-*"],
+			"priority": 1,
+			"template": {
+				"settings": {
+					"number_of_shards": 2,
+					"number_of_replicas": 0
+				},
+				"mappings": {
+					"_source": { "enabled": true }
+				}
+			}
+		}`).Do(context.Background())
+		if err != nil {
+			t.Fatalf("expected to successfully create index template, got %v", err)
+		}
+		if resp == nil {
+			t.Fatal("expected response on creating index template")
+		}
+		if want, have := true, resp.Acknowledged; want != have {
+			t.Errorf("expected Acknowledged=%v, got %v", want, have)
+		}
+	}
+
+	// Get the index template
+	{
+		resp, err := client.IndexGetIndexTemplate(templateName).Pretty(true).Do(context.Background())
+		if err != nil {
+			t.Fatalf("expected to successfully get index template, got %v", err)
+		}
+		if resp == nil {
+			t.Fatal("expected response on getting index template")
+		}
+	}
+
+	// Delete the index template
+	{
+		resp, err := client.IndexDeleteIndexTemplate(templateName).Pretty(true).Do(context.Background())
+		if err != nil {
+			t.Fatalf("expected to successfully delete index template, got %v", err)
+		}
+		if resp == nil {
+			t.Fatal("expected response on deleting index template")
+		}
+	}
+}