From e556f3c526bf92994e23a6794799ba6d6e739761 Mon Sep 17 00:00:00 2001 From: Yann Soubeyrand Date: Thu, 4 Feb 2021 18:04:32 +0100 Subject: [PATCH] Add support for ElasticSearch 7.8+ component templates Signed-off-by: Yann Soubeyrand --- es/diff_suppress_funcs.go | 20 ++ es/provider.go | 1 + ...source_elasticsearch_component_template.go | 178 +++++++++++++++++ ...e_elasticsearch_component_template_test.go | 189 ++++++++++++++++++ es/util.go | 13 ++ go.mod | 2 + go.sum | 17 +- 7 files changed, 405 insertions(+), 15 deletions(-) create mode 100644 es/resource_elasticsearch_component_template.go create mode 100644 es/resource_elasticsearch_component_template_test.go diff --git a/es/diff_suppress_funcs.go b/es/diff_suppress_funcs.go index b29c8b5f..e23cbbe6 100644 --- a/es/diff_suppress_funcs.go +++ b/es/diff_suppress_funcs.go @@ -51,6 +51,26 @@ func diffSuppressComposableIndexTemplate(k, old, new string, d *schema.ResourceD return reflect.DeepEqual(oo, no) } +func diffSuppressComponentTemplate(k, old, new string, d *schema.ResourceData) bool { + var oo, no interface{} + if err := json.Unmarshal([]byte(old), &oo); err != nil { + return false + } + if err := json.Unmarshal([]byte(new), &no); err != nil { + return false + } + + if om, ok := oo.(map[string]interface{}); ok { + normalizeComponentTemplate(om) + } + + if nm, ok := no.(map[string]interface{}); ok { + normalizeComponentTemplate(nm) + } + + return reflect.DeepEqual(oo, no) +} + func diffSuppressDestination(k, old, new string, d *schema.ResourceData) bool { var oo, no interface{} if err := json.Unmarshal([]byte(old), &oo); err != nil { diff --git a/es/provider.go b/es/provider.go index 21d652f7..4ac4e52e 100644 --- a/es/provider.go +++ b/es/provider.go @@ -169,6 +169,7 @@ func Provider() terraform.ResourceProvider { "elasticsearch_index_lifecycle_policy": resourceElasticsearchDeprecatedIndexLifecyclePolicy(), "elasticsearch_index_template": resourceElasticsearchIndexTemplate(), "elasticsearch_composable_index_template": resourceElasticsearchComposableIndexTemplate(), + "elasticsearch_component_template": resourceElasticsearchComponentTemplate(), "elasticsearch_ingest_pipeline": resourceElasticsearchIngestPipeline(), "elasticsearch_kibana_object": resourceElasticsearchKibanaObject(), "elasticsearch_monitor": resourceElasticsearchDeprecatedMonitor(), diff --git a/es/resource_elasticsearch_component_template.go b/es/resource_elasticsearch_component_template.go new file mode 100644 index 00000000..16b616dc --- /dev/null +++ b/es/resource_elasticsearch_component_template.go @@ -0,0 +1,178 @@ +package es + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + elastic7 "github.com/olivere/elastic/v7" +) + +var componentTemplateMinimalVersion, _ = version.NewVersion("7.8.0") + +func resourceElasticsearchComponentTemplate() *schema.Resource { + return &schema.Resource{ + Create: resourceElasticsearchComponentTemplateCreate, + Read: resourceElasticsearchComponentTemplateRead, + Update: resourceElasticsearchComponentTemplateUpdate, + Delete: resourceElasticsearchComponentTemplateDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "body": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: diffSuppressComponentTemplate, + ValidateFunc: validation.StringIsJSON, + }, + }, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + } +} + +func resourceElasticsearchComponentTemplateCreate(d *schema.ResourceData, meta interface{}) error { + err := resourceElasticsearchPutComponentTemplate(d, meta, true) + if err != nil { + return err + } + d.SetId(d.Get("name").(string)) + return nil +} + +func resourceElasticsearchComponentTemplateRead(d *schema.ResourceData, meta interface{}) error { + id := d.Id() + + var result string + var elasticVersion *version.Version + + esClient, err := getClient(meta.(*ProviderConf)) + if err != nil { + return err + } + + switch client := esClient.(type) { + case *elastic7.Client: + elasticVersion, err = elastic7GetVersion(client) + if err == nil { + if elasticVersion.LessThan(componentTemplateMinimalVersion) { + err = fmt.Errorf("component_template endpoint only available from ElasticSearch >= 7.8, got version %s", elasticVersion.String()) + } else { + result, err = elastic7GetComponentTemplate(client, id) + } + } + default: + err = fmt.Errorf("component_template endpoint only available from ElasticSearch >= 7.8, got version < 7.0.0") + } + if err != nil { + if elastic7.IsNotFound(err) { + log.Printf("[WARN] Index template (%s) not found, removing from state", id) + d.SetId("") + return nil + } + + return err + } + + ds := &resourceDataSetter{d: d} + ds.set("name", d.Id()) + ds.set("body", result) + return ds.err +} + +func elastic7GetComponentTemplate(client *elastic7.Client, id string) (string, error) { + res, err := client.IndexGetComponentTemplate(id).Do(context.TODO()) + if err != nil { + return "", err + } + + // No more than 1 element is expected, if the index template is not found, previous call should + // return a 404 error + t := res.ComponentTemplates[0].ComponentTemplate + tj, err := json.Marshal(t) + if err != nil { + return "", err + } + return string(tj), nil +} + +func resourceElasticsearchComponentTemplateUpdate(d *schema.ResourceData, meta interface{}) error { + return resourceElasticsearchPutComponentTemplate(d, meta, false) +} + +func resourceElasticsearchComponentTemplateDelete(d *schema.ResourceData, meta interface{}) error { + id := d.Id() + + var elasticVersion *version.Version + + esClient, err := getClient(meta.(*ProviderConf)) + if err != nil { + return err + } + + switch client := esClient.(type) { + case *elastic7.Client: + elasticVersion, err = elastic7GetVersion(client) + if err == nil { + if elasticVersion.LessThan(componentTemplateMinimalVersion) { + err = fmt.Errorf("component_template endpoint only available from ElasticSearch >= 7.8, got version %s", elasticVersion.String()) + } else { + err = elastic7DeleteComponentTemplate(client, id) + } + } + default: + err = fmt.Errorf("component_template endpoint only available from ElasticSearch >= 7.8, got version < 7.0.0") + } + + if err != nil { + return err + } + d.SetId("") + return nil +} + +func elastic7DeleteComponentTemplate(client *elastic7.Client, id string) error { + _, err := client.IndexDeleteComponentTemplate(id).Do(context.TODO()) + return err +} + +func resourceElasticsearchPutComponentTemplate(d *schema.ResourceData, meta interface{}, create bool) error { + name := d.Get("name").(string) + body := d.Get("body").(string) + + var elasticVersion *version.Version + + esClient, err := getClient(meta.(*ProviderConf)) + if err != nil { + return err + } + + switch client := esClient.(type) { + case *elastic7.Client: + elasticVersion, err = elastic7GetVersion(client) + if err == nil { + if elasticVersion.LessThan(componentTemplateMinimalVersion) { + err = fmt.Errorf("component_template endpoint only available from ElasticSearch >= 7.8, got version %s", elasticVersion.String()) + } else { + err = elastic7PutComponentTemplate(client, name, body, create) + } + } + default: + err = fmt.Errorf("component_template endpoint only available from ElasticSearch >= 7.8, got version < 7.0.0") + } + + return err +} + +func elastic7PutComponentTemplate(client *elastic7.Client, name string, body string, create bool) error { + _, err := client.IndexPutComponentTemplate(name).BodyString(body).Create(create).Do(context.TODO()) + return err +} diff --git a/es/resource_elasticsearch_component_template_test.go b/es/resource_elasticsearch_component_template_test.go new file mode 100644 index 00000000..257fb470 --- /dev/null +++ b/es/resource_elasticsearch_component_template_test.go @@ -0,0 +1,189 @@ +package es + +import ( + "context" + "errors" + "fmt" + "testing" + + elastic7 "github.com/olivere/elastic/v7" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccElasticsearchComponentTemplate(t *testing.T) { + provider := Provider().(*schema.Provider) + err := provider.Configure(&terraform.ResourceConfig{}) + if err != nil { + t.Skipf("err: %s", err) + } + meta := provider.Meta() + + esClient, err := getClient(meta.(*ProviderConf)) + if err != nil { + t.Skipf("err: %s", err) + } + + var allowed bool + switch esClient.(type) { + case *elastic7.Client: + allowed = true + default: + allowed = false + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + if !allowed { + t.Skip("/_component_template endpoint only supported on ES >= 7.8") + } + }, + Providers: testAccProviders, + CheckDestroy: testCheckElasticsearchComponentTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccElasticsearchComponentTemplate, + Check: resource.ComposeTestCheckFunc( + testCheckElasticsearchComponentTemplateExists("elasticsearch_component_template.test"), + ), + }, + }, + }) +} + +func TestAccElasticsearchComponentTemplate_importBasic(t *testing.T) { + provider := Provider().(*schema.Provider) + err := provider.Configure(&terraform.ResourceConfig{}) + if err != nil { + t.Skipf("err: %s", err) + } + meta := provider.Meta() + + esClient, err := getClient(meta.(*ProviderConf)) + if err != nil { + t.Skipf("err: %s", err) + } + + var allowed bool + switch esClient.(type) { + case *elastic7.Client: + allowed = true + default: + allowed = false + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + if !allowed { + t.Skip("/_component_template endpoint only supported on ES >= 7.8") + } + }, + Providers: testAccProviders, + CheckDestroy: testCheckElasticsearchComponentTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccElasticsearchComponentTemplate, + }, + { + ResourceName: "elasticsearch_component_template.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckElasticsearchComponentTemplateExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No component template ID is set") + } + + meta := testAccProvider.Meta() + + esClient, err := getClient(meta.(*ProviderConf)) + if err != nil { + return err + } + + switch client := esClient.(type) { + case *elastic7.Client: + _, err = client.IndexGetComponentTemplate(rs.Primary.ID).Do(context.TODO()) + default: + err = errors.New("/_component_template endpoint only supported on ES >= 7.8") + } + + if err != nil { + return err + } + + return nil + } +} + +func testCheckElasticsearchComponentTemplateDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "elasticsearch_component_template" { + continue + } + + meta := testAccProvider.Meta() + + esClient, err := getClient(meta.(*ProviderConf)) + if err != nil { + return err + } + + switch client := esClient.(type) { + case *elastic7.Client: + _, err = client.IndexGetComponentTemplate(rs.Primary.ID).Do(context.TODO()) + default: + err = errors.New("/_component_template endpoint only supported on ES >= 7.8") + } + + if err != nil { + return nil // should be not found error + } + + return fmt.Errorf("Component template %q still exists", rs.Primary.ID) + } + + return nil +} + +var testAccElasticsearchComponentTemplate = ` +resource "elasticsearch_component_template" "test" { + name = "terraform-test" + body = < github.com/camptocamp/elastic/v7 v7.0.23-0.20210204165908-e3d81ffeb2ca diff --git a/go.sum b/go.sum index 10971b6c..4e17cc9d 100644 --- a/go.sum +++ b/go.sum @@ -26,14 +26,14 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.29.11 h1:f1QJRPu30p0i1lzKhkSSaZFudFGCra2HKgdE442nN6c= -github.com/aws/aws-sdk-go v1.29.11/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/aws/aws-sdk-go v1.35.20 h1:Hs7x9Czh+MMPnZLQqHhsuZKeNFA3Vuf7pdy2r5QlVb0= github.com/aws/aws-sdk-go v1.35.20/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/camptocamp/elastic/v7 v7.0.23-0.20210204165908-e3d81ffeb2ca h1:CyuZfYRC+MeTJCbVGzAuEPJvtD0ZkUP6zGBRiyGJC34= +github.com/camptocamp/elastic/v7 v7.0.23-0.20210204165908-e3d81ffeb2ca/go.mod h1:VDexNy9NjmtAkrjNoI7tImv7FR4tf5zUA3ickqu5Pc8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -74,8 +74,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= @@ -144,8 +142,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -190,11 +186,6 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olivere/elastic v6.2.26+incompatible h1:3PjUHKyt8xKwbFQpRC5cgtEY7Qz6ejopBkukhI7UWvE= github.com/olivere/elastic v6.2.26+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= -github.com/olivere/elastic/v7 v7.0.12 h1:91kj/UMKWQt8VAHBm5BDHpVmzdfPCmICaUFy2oH4LkQ= -github.com/olivere/elastic/v7 v7.0.12/go.mod h1:14rWX28Pnh3qCKYRVnSGXWLf9MbLonYS/4FDCY3LAPo= -github.com/olivere/elastic/v7 v7.0.22 h1:esBA6JJwvYgfms0EVlH7Z+9J4oQ/WUADF2y/nCNDw7s= -github.com/olivere/elastic/v7 v7.0.22/go.mod h1:VDexNy9NjmtAkrjNoI7tImv7FR4tf5zUA3ickqu5Pc8= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -207,10 +198,8 @@ github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DK github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= -github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -239,8 +228,6 @@ github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpUR github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=