diff --git a/.changelog/264.txt b/.changelog/264.txt new file mode 100644 index 000000000..eab7ec569 --- /dev/null +++ b/.changelog/264.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +datasource/ec_deployment: Adds a new `elasticsearch.extension` block which can be used to enable custom Elasticsearch bundles or plugins that have previously been uploaded. +``` diff --git a/docs/resources/ec_deployment.md b/docs/resources/ec_deployment.md index 0adc85e6b..1c0447268 100644 --- a/docs/resources/ec_deployment.md +++ b/docs/resources/ec_deployment.md @@ -223,6 +223,7 @@ The required `elasticsearch` block supports the following arguments: * `config` (Optional) Elasticsearch settings applied to all topologies unless overridden in the `topology` element. * `remote_cluster` (Optional) Elasticsearch remote clusters to configure for the Elasticsearch resource. Can be set multiple times. * `snapshot_source` (Optional) Restores data from a snapshot of another deployment. +* `extension` (Optional) Custom Elasticsearch bundles or plugins. Can be set multiple times. ##### Topology @@ -269,6 +270,15 @@ The optional `elasticsearch.snapshot_source` block, which restores data from a s ~> **Note on behavior** The `snapshot_source` block will not be saved in the Terraform state due to its transient nature. This means that whenever the `snapshot_source` block is set, a snapshot will **always be restored**, unless removed before running `terraform apply`. +##### Extension + +The optional `elasticsearch.extension` block, allows custom plugins or bundles to be configured in the Elasticsearch cluster. It supports the following arguments: + +* `name` (Required) Extension name. +* `type` (Required) Extension type, only `bundle` or `plugin` are supported. +* `version` (Required) Elasticsearch compatibility version. Bundles should specify major or minor versions with wildcards, such as `7.*` or `*` but **plugins must use full version notation down to the patch level**, such as `7.10.1` and wildcards are not allowed. +* `url` (Required) Bundle or plugin URL, the extension URL can be obtained from the `ec_deployment_extension..url` attribute or the API and cannot be a random HTTP address that is hosted elsewhere. + #### Kibana The optional `kibana` block supports the following arguments: diff --git a/docs/resources/ec_deployment_extension.md b/docs/resources/ec_deployment_extension.md index 2ba1bed99..b874710bd 100644 --- a/docs/resources/ec_deployment_extension.md +++ b/docs/resources/ec_deployment_extension.md @@ -39,6 +39,41 @@ resource "ec_deployment_extension" "example_extension" { } ``` +### Using extension in ec_deployment +```hcl +resource "ec_deployment_extension" "example_extension" { + name = "my_extension" + description = "my extension" + version = "*" + extension_type = "bundle" + download_url = "https://example.net" +} + +data "ec_stack" "latest" { + version_regex = "latest" + region = "us-east-1" +} + +resource "ec_deployment" "with_extension" { + # Optional name. + name = "my_example_deployment" + + # Mandatory fields + region = "us-east-1" + version = data.ec_stack.latest.version + deployment_template_id = "aws-io-optimized-v2" + + elasticsearch { + extension { + name = ec_deployment_extension.example_extension.name + type = "bundle" + version = data.ec_stack.latest.version + url = ec_deployment_extension.example_extension.url + } + } +} +``` + ## Argument Reference The following arguments are supported: diff --git a/ec/acc/deployment_with_extension_bundle_test.go b/ec/acc/deployment_with_extension_bundle_test.go new file mode 100644 index 000000000..c05ab8e91 --- /dev/null +++ b/ec/acc/deployment_with_extension_bundle_test.go @@ -0,0 +1,94 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. 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 acc + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/elastic/cloud-sdk-go/pkg/multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccDeployment_withExtension(t *testing.T) { + extResName := "ec_deployment_extension.my_extension" + resName := "ec_deployment.with_extension" + randomName := prefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + filePath := filepath.Join(os.TempDir(), "extension.zip") + defer os.Remove(filePath) + + cfg := fixtureAccDeploymentWithExtensionBundle(t, + "testdata/deployment_with_extension_bundle_file.tf", + getRegion(), randomName, "desc", filePath, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactory, + CheckDestroy: func(s *terraform.State) error { + merr := multierror.NewPrefixed("checking resource with extension") + + if err := testAccExtensionDestroy(s); err != nil { + merr = merr.Append(err) + } + if err := testAccDeploymentDestroy(s); err != nil { + merr = merr.Append(err) + } + + return merr.ErrorOrNil() + }, + Steps: []resource.TestStep{ + { + PreConfig: func() { writeFile(t, filePath, "extension.txt", "foo") }, + Config: cfg, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(extResName, "name", randomName), + resource.TestCheckResourceAttr(extResName, "version", "*"), + resource.TestCheckResourceAttr(extResName, "description", "desc"), + resource.TestCheckResourceAttr(extResName, "extension_type", "bundle"), + resource.TestCheckResourceAttr(extResName, "file_path", filePath), + resource.TestCheckResourceAttr(resName, "elasticsearch.0.extension.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resName, "elasticsearch.0.extension.*", map[string]string{ + "type": "bundle", + "name": randomName, + }), + func(s *terraform.State) error { + return checkExtensionFile(t, s, "extension.txt", "foo") + }, + ), + }, + }, + }) +} + +func fixtureAccDeploymentWithExtensionBundle(t *testing.T, filepath, region, name, desc, file string) string { + t.Helper() + + b, err := os.ReadFile(filepath) + if err != nil { + t.Fatal(err) + } + return fmt.Sprintf(string(b), region, + setDefaultTemplate(region, defaultTemplate), name, desc, file, + ) +} diff --git a/ec/acc/testdata/deployment_with_extension_bundle_file.tf b/ec/acc/testdata/deployment_with_extension_bundle_file.tf new file mode 100644 index 000000000..e44aa9d06 --- /dev/null +++ b/ec/acc/testdata/deployment_with_extension_bundle_file.tf @@ -0,0 +1,39 @@ +locals { + region = "%s" + deployment_template = "%s" + name = "%s" + description = "%s" + file_path = "%s" +} + + +data "ec_stack" "latest" { + version_regex = "latest" + region = local.region +} + +resource "ec_deployment" "with_extension" { + name = local.name + region = local.region + version = data.ec_stack.latest.version + deployment_template_id = local.deployment_template + + elasticsearch { + extension { + type = "bundle" + name = local.name + version = data.ec_stack.latest.version + url = ec_deployment_extension.my_extension.url + } + } +} + +resource "ec_deployment_extension" "my_extension" { + name = local.name + description = local.description + version = "*" + extension_type = "bundle" + + file_path = local.file_path + file_hash = filebase64sha256(local.file_path) +} diff --git a/ec/ecresource/deploymentresource/elasticsearch_expanders.go b/ec/ecresource/deploymentresource/elasticsearch_expanders.go index 1f6833cb7..f2dba2d5f 100644 --- a/ec/ecresource/deploymentresource/elasticsearch_expanders.go +++ b/ec/ecresource/deploymentresource/elasticsearch_expanders.go @@ -89,6 +89,12 @@ func expandEsResource(raw interface{}, res *models.ElasticsearchPayload) (*model expandSnapshotSource(snap, res.Plan.Transient.RestoreSnapshot) } + if ext, ok := es["extension"]; ok { + if e := ext.(*schema.Set); e.Len() > 0 { + expandEsExtension(e.List(), res.Plan.Elasticsearch) + } + } + return res, nil } @@ -332,3 +338,40 @@ func sizeIsEmpty(size *models.TopologySize) bool { return false } + +func expandEsExtension(raw []interface{}, es *models.ElasticsearchConfiguration) { + for _, rawExt := range raw { + m := rawExt.(map[string]interface{}) + + var version string + if v, ok := m["version"]; ok { + version = v.(string) + } + + var url string + if u, ok := m["url"]; ok { + url = u.(string) + } + + var name string + if n, ok := m["name"]; ok { + name = n.(string) + } + + if t, ok := m["type"]; ok && t.(string) == "bundle" { + es.UserBundles = append(es.UserBundles, &models.ElasticsearchUserBundle{ + Name: &name, + ElasticsearchVersion: &version, + URL: &url, + }) + } + + if t, ok := m["type"]; ok && t.(string) == "plugin" { + es.UserPlugins = append(es.UserPlugins, &models.ElasticsearchUserPlugin{ + Name: &name, + ElasticsearchVersion: &version, + URL: &url, + }) + } + } +} diff --git a/ec/ecresource/deploymentresource/elasticsearch_flatteners.go b/ec/ecresource/deploymentresource/elasticsearch_flatteners.go index 1e2ba42a5..8c2d896ca 100644 --- a/ec/ecresource/deploymentresource/elasticsearch_flatteners.go +++ b/ec/ecresource/deploymentresource/elasticsearch_flatteners.go @@ -71,6 +71,19 @@ func flattenEsResources(in []*models.ElasticsearchResourceInfo, name string, rem m["remote_cluster"] = r } + extensions := schema.NewSet(esExtensionHash, nil) + for _, ext := range flattenEsBundles(plan.Elasticsearch.UserBundles) { + extensions.Add(ext) + } + + for _, ext := range flattenEsPlugins(plan.Elasticsearch.UserPlugins) { + extensions.Add(ext) + } + + if extensions.Len() > 0 { + m["extension"] = extensions + } + result = append(result, m) } @@ -197,3 +210,33 @@ func flattenEsRemotes(in models.RemoteResources) []interface{} { return res } + +func flattenEsBundles(in []*models.ElasticsearchUserBundle) []interface{} { + result := make([]interface{}, 0, len(in)) + for _, bundle := range in { + m := make(map[string]interface{}) + m["type"] = "bundle" + m["version"] = *bundle.ElasticsearchVersion + m["url"] = *bundle.URL + m["name"] = *bundle.Name + + result = append(result, m) + } + + return result +} + +func flattenEsPlugins(in []*models.ElasticsearchUserPlugin) []interface{} { + result := make([]interface{}, 0, len(in)) + for _, plugin := range in { + m := make(map[string]interface{}) + m["type"] = "plugin" + m["version"] = *plugin.ElasticsearchVersion + m["url"] = *plugin.URL + m["name"] = *plugin.Name + + result = append(result, m) + } + + return result +} diff --git a/ec/ecresource/deploymentresource/expanders_test.go b/ec/ecresource/deploymentresource/expanders_test.go index 6bfc4ad94..f962858e3 100644 --- a/ec/ecresource/deploymentresource/expanders_test.go +++ b/ec/ecresource/deploymentresource/expanders_test.go @@ -1271,6 +1271,152 @@ func Test_createResourceToModel(t *testing.T) { }, }, }, + { + name: "parses the resources with empty declarations (Hot Warm) with node_roles and extensions", + args: args{ + d: util.NewResourceData(t, util.ResDataParams{ + ID: mock.ValidClusterID, + Schema: newSchema(), + State: map[string]interface{}{ + "name": "my_deployment_name", + "deployment_template_id": "aws-hot-warm-v2", + "region": "us-east-1", + "version": "7.12.0", + "elasticsearch": []interface{}{map[string]interface{}{ + "extension": []interface{}{ + map[string]interface{}{ + "name": "my-plugin", + "type": "plugin", + "url": "repo://12311234", + "version": "7.7.0", + }, + map[string]interface{}{ + "name": "my-second-plugin", + "type": "plugin", + "url": "repo://12311235", + "version": "7.7.0", + }, + map[string]interface{}{ + "name": "my-bundle", + "type": "bundle", + "url": "repo://1231122", + "version": "7.7.0", + }, + map[string]interface{}{ + "name": "my-second-bundle", + "type": "bundle", + "url": "repo://1231123", + "version": "7.7.0", + }, + }, + }}, + }, + }), + client: api.NewMock(mock.New200Response(hotWarmTpl())), + }, + want: &models.DeploymentCreateRequest{ + Name: "my_deployment_name", + Settings: &models.DeploymentCreateSettings{}, + Metadata: &models.DeploymentCreateMetadata{ + Tags: []*models.MetadataItem{}, + }, + Resources: &models.DeploymentCreateResources{ + Elasticsearch: []*models.ElasticsearchPayload{ + { + Region: ec.String("us-east-1"), + RefID: ec.String("main-elasticsearch"), + Settings: &models.ElasticsearchClusterSettings{ + DedicatedMastersThreshold: 6, + }, + Plan: &models.ElasticsearchClusterPlan{ + Elasticsearch: &models.ElasticsearchConfiguration{ + Version: "7.12.0", + UserBundles: []*models.ElasticsearchUserBundle{ + { + URL: ec.String("repo://1231122"), + Name: ec.String("my-bundle"), + ElasticsearchVersion: ec.String("7.7.0"), + }, + { + URL: ec.String("repo://1231123"), + Name: ec.String("my-second-bundle"), + ElasticsearchVersion: ec.String("7.7.0"), + }, + }, + UserPlugins: []*models.ElasticsearchUserPlugin{ + { + URL: ec.String("repo://12311235"), + Name: ec.String("my-second-plugin"), + ElasticsearchVersion: ec.String("7.7.0"), + }, + { + URL: ec.String("repo://12311234"), + Name: ec.String("my-plugin"), + ElasticsearchVersion: ec.String("7.7.0"), + }, + }, + }, + DeploymentTemplate: &models.DeploymentTemplateReference{ + ID: ec.String("aws-hot-warm-v2"), + }, + ClusterTopology: []*models.ElasticsearchClusterTopologyElement{ + { + ID: "hot_content", + ZoneCount: 2, + InstanceConfigurationID: "aws.data.highio.i3", + Size: &models.TopologySize{ + Resource: ec.String("memory"), + Value: ec.Int32(4096), + }, + NodeRoles: []string{ + "master", + "ingest", + "remote_cluster_client", + "data_hot", + "transform", + "data_content", + }, + Elasticsearch: &models.ElasticsearchConfiguration{ + NodeAttributes: map[string]string{"data": "hot"}, + }, + TopologyElementControl: &models.TopologyElementControl{ + Min: &models.TopologySize{ + Resource: ec.String("memory"), + Value: ec.Int32(1024), + }, + }, + }, + { + ID: "warm", + ZoneCount: 2, + InstanceConfigurationID: "aws.data.highstorage.d2", + Size: &models.TopologySize{ + Resource: ec.String("memory"), + Value: ec.Int32(4096), + }, + NodeRoles: []string{ + "data_warm", + "remote_cluster_client", + }, + Elasticsearch: &models.ElasticsearchConfiguration{ + NodeAttributes: map[string]string{ + "data": "warm", + }, + }, + TopologyElementControl: &models.TopologyElementControl{ + Min: &models.TopologySize{ + Resource: ec.String("memory"), + Value: ec.Int32(0), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { name: "parses the resources with empty declarations (Cross Cluster Search)", args: args{ diff --git a/ec/ecresource/deploymentresource/flatteners_test.go b/ec/ecresource/deploymentresource/flatteners_test.go index 76864349f..5217e02f3 100644 --- a/ec/ecresource/deploymentresource/flatteners_test.go +++ b/ec/ecresource/deploymentresource/flatteners_test.go @@ -168,6 +168,9 @@ func Test_modelToState(t *testing.T) { Schema: newSchema(), }) + awsIOOptimizedExtensionRD := schema.TestResourceDataRaw(t, newSchema(), nil) + awsIOOptimizedExtensionRD.SetId(mock.ValidClusterID) + awsIOOptimizedTagsRes := openDeploymentGet(t, "testdata/deployment-aws-io-optimized-tags.json") awsIOOptimizedTagsRD := schema.TestResourceDataRaw(t, newSchema(), nil) awsIOOptimizedTagsRD.SetId(mock.ValidClusterID) @@ -743,6 +746,99 @@ func Test_modelToState(t *testing.T) { args: args{d: awsIOOptimizedRD, res: awsIOOptimizedRes}, want: wantAwsIOOptimizedDeployment, }, + { + name: "flattens an aws plan with extensions (io-optimized)", + args: args{ + d: awsIOOptimizedExtensionRD, + res: openDeploymentGet(t, "testdata/deployment-aws-io-optimized-extension.json"), + }, + want: util.NewResourceData(t, util.ResDataParams{ + ID: mock.ValidClusterID, + State: map[string]interface{}{ + "deployment_template_id": "aws-io-optimized-v2", + "id": "123b7b540dfc967a7a649c18e2fce4ed", + "name": "up2d", + "region": "aws-eu-central-1", + "version": "7.9.2", + "apm": []interface{}{map[string]interface{}{ + "elasticsearch_cluster_ref_id": "main-elasticsearch", + "ref_id": "main-apm", + "region": "aws-eu-central-1", + "resource_id": "12328579b3bf40c8b58c1a0ed5a4bd8b", + "version": "7.9.2", + "http_endpoint": "http://12328579b3bf40c8b58c1a0ed5a4bd8b.apm.eu-central-1.aws.cloud.es.io:80", + "https_endpoint": "https://12328579b3bf40c8b58c1a0ed5a4bd8b.apm.eu-central-1.aws.cloud.es.io:443", + "topology": []interface{}{map[string]interface{}{ + "instance_configuration_id": "aws.apm.r5d", + "size": "0.5g", + "size_resource": "memory", + "zone_count": 1, + }}, + }}, + "elasticsearch": []interface{}{map[string]interface{}{ + "cloud_id": "up2d:someCloudID", + "extension": []interface{}{ + map[string]interface{}{ + "name": "custom-bundle", + "version": "7.9.2", + "url": "http://12345", + "type": "bundle", + }, + map[string]interface{}{ + "name": "custom-bundle2", + "version": "7.9.2", + "url": "http://123456", + "type": "bundle", + }, + map[string]interface{}{ + "name": "custom-plugin", + "version": "7.9.2", + "url": "http://12345", + "type": "plugin", + }, + map[string]interface{}{ + "name": "custom-plugin2", + "version": "7.9.2", + "url": "http://123456", + "type": "plugin", + }, + }, + "http_endpoint": "http://1239f7ee7196439ba2d105319ac5eba7.eu-central-1.aws.cloud.es.io:9200", + "https_endpoint": "https://1239f7ee7196439ba2d105319ac5eba7.eu-central-1.aws.cloud.es.io:9243", + "ref_id": "main-elasticsearch", + "region": "aws-eu-central-1", + "resource_id": "1239f7ee7196439ba2d105319ac5eba7", + "topology": []interface{}{map[string]interface{}{ + "id": "hot_content", + "instance_configuration_id": "aws.data.highio.i3", + "node_type_data": "true", + "node_type_ingest": "true", + "node_type_master": "true", + "node_type_ml": "false", + "size": "8g", + "size_resource": "memory", + "zone_count": 2, + }}, + }}, + "kibana": []interface{}{map[string]interface{}{ + "elasticsearch_cluster_ref_id": "main-elasticsearch", + "ref_id": "main-kibana", + "region": "aws-eu-central-1", + "resource_id": "123dcfda06254ca789eb287e8b73ff4c", + "version": "7.9.2", + "http_endpoint": "http://123dcfda06254ca789eb287e8b73ff4c.eu-central-1.aws.cloud.es.io:9200", + "https_endpoint": "https://123dcfda06254ca789eb287e8b73ff4c.eu-central-1.aws.cloud.es.io:9243", + "topology": []interface{}{map[string]interface{}{ + "instance_configuration_id": "aws.kibana.r5d", + "size": "1g", + "size_resource": "memory", + "zone_count": 1, + }}, + }}, + }, + Schema: newSchema(), + }), + }, { name: "flattens an aws plan (io-optimized) with tags", args: args{d: awsIOOptimizedTagsRD, res: awsIOOptimizedTagsRes}, diff --git a/ec/ecresource/deploymentresource/import_test.go b/ec/ecresource/deploymentresource/import_test.go index b00d26035..9b09bc3a1 100644 --- a/ec/ecresource/deploymentresource/import_test.go +++ b/ec/ecresource/deploymentresource/import_test.go @@ -110,6 +110,7 @@ func Test_importFunc(t *testing.T) { "elasticsearch.0.cloud_id": "", "elasticsearch.0.snapshot_source.#": "0", "elasticsearch.0.config.#": "0", + "elasticsearch.0.extension.#": "0", "elasticsearch.0.http_endpoint": "", "elasticsearch.0.https_endpoint": "", "elasticsearch.0.ref_id": "main-elasticsearch", @@ -154,6 +155,7 @@ func Test_importFunc(t *testing.T) { "elasticsearch.0.cloud_id": "", "elasticsearch.0.snapshot_source.#": "0", "elasticsearch.0.config.#": "0", + "elasticsearch.0.extension.#": "0", "elasticsearch.0.http_endpoint": "", "elasticsearch.0.https_endpoint": "", "elasticsearch.0.ref_id": "main-elasticsearch", @@ -198,6 +200,7 @@ func Test_importFunc(t *testing.T) { "elasticsearch.0.cloud_id": "", "elasticsearch.0.snapshot_source.#": "0", "elasticsearch.0.config.#": "0", + "elasticsearch.0.extension.#": "0", "elasticsearch.0.http_endpoint": "", "elasticsearch.0.https_endpoint": "", "elasticsearch.0.ref_id": "main-elasticsearch", diff --git a/ec/ecresource/deploymentresource/schema_elasticsearch.go b/ec/ecresource/deploymentresource/schema_elasticsearch.go index 43f3b17ea..18db979e0 100644 --- a/ec/ecresource/deploymentresource/schema_elasticsearch.go +++ b/ec/ecresource/deploymentresource/schema_elasticsearch.go @@ -18,6 +18,10 @@ package deploymentresource import ( + "bytes" + "fmt" + + "github.com/elastic/cloud-sdk-go/pkg/util/slice" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) @@ -67,6 +71,8 @@ func newElasticsearchResource() *schema.Resource { "remote_cluster": elasticsearchRemoteCluster(), "snapshot_source": newSnapshotSourceSettings(), + + "extension": newExtensionSchema(), }, } } @@ -261,3 +267,57 @@ func newSnapshotSourceSettings() *schema.Schema { }, } } + +func newExtensionSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Set: esExtensionHash, + Description: "Optional Elasticsearch extensions such as custom bundles or plugins.", + Optional: true, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Description: "Extension name.", + Type: schema.TypeString, + Required: true, + }, + "type": { + Description: "Extension type, only `bundle` or `plugin` are supported.", + Type: schema.TypeString, + Required: true, + ValidateFunc: func(val interface{}, _ string) ([]string, []error) { + t := val.(string) + if !slice.HasString([]string{"bundle", "plugin"}, t) { + return nil, []error{fmt.Errorf( + "invalid extension type %s: accepted values are bundle or plugin", + t, + )} + } + return nil, nil + }, + }, + "version": { + Description: "Elasticsearch compatibility version. Bundles should specify major or minor versions with wildcards, such as `7.*` or `*` but **plugins must use full version notation down to the patch level**, such as `7.10.1` and wildcards are not allowed.", + Type: schema.TypeString, + Required: true, + }, + "url": { + Description: "Bundle or plugin URL, the extension URL can be obtained from the `ec_deployment_extension..url` attribute or the API and cannot be a random HTTP address that is hosted elsewhere.", + Type: schema.TypeString, + Required: true, + }, + }, + }, + } +} + +func esExtensionHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(m["type"].(string)) + buf.WriteString(m["version"].(string)) + buf.WriteString(m["url"].(string)) + buf.WriteString(m["name"].(string)) + return schema.HashString(buf.String()) +} diff --git a/ec/ecresource/deploymentresource/testdata/deployment-aws-io-optimized-extension.json b/ec/ecresource/deploymentresource/testdata/deployment-aws-io-optimized-extension.json new file mode 100644 index 000000000..c24ff3894 --- /dev/null +++ b/ec/ecresource/deploymentresource/testdata/deployment-aws-io-optimized-extension.json @@ -0,0 +1,378 @@ +{ + "healthy": true, + "id": "123365f2805e46808d40849b1c0b266b", + "name": "up2d", + "resources": { + "apm": [ + { + "elasticsearch_cluster_ref_id": "main-elasticsearch", + "id": "12328579b3bf40c8b58c1a0ed5a4bd8b", + "info": { + "deployment_id": "123365f2805e46808d40849b1c0b266b", + "elasticsearch_cluster": { + "elasticsearch_id": "1239f7ee7196439ba2d105319ac5eba7" + }, + "external_links": [], + "healthy": true, + "id": "12328579b3bf40c8b58c1a0ed5a4bd8b", + "metadata": { + "endpoint": "12328579b3bf40c8b58c1a0ed5a4bd8b.apm.eu-central-1.aws.cloud.es.io", + "last_modified": "2020-10-14T05:02:27.645Z", + "ports": { + "http": 80, + "https": 443, + "transport_passthrough": 9400 + }, + "version": 7 + }, + "name": "up2d", + "plan_info": { + "current": { + "attempt_end_time": "2020-10-14T05:04:55.439Z", + "attempt_start_time": "2020-10-14T05:02:27.266Z", + "healthy": true, + "plan": { + "apm": { + "system_settings": { + "secret_token": "yMpNQNOBVxZhlgFnBY" + }, + "version": "7.9.2" + }, + "cluster_topology": [ + { + "apm": { + "system_settings": { + "debug_enabled": false, + "secret_token": "yMpNQNOBVxZhlgFnBY" + } + }, + "instance_configuration_id": "aws.apm.r5d", + "size": { + "resource": "memory", + "value": 512 + }, + "zone_count": 1 + } + ] + }, + "plan_attempt_id": "26dd8a24-c8e2-42a4-ad7a-13ddf8c77b43", + "plan_attempt_log": [], + "plan_end_time": "0001-01-01T00:00:00.000Z" + }, + "healthy": true, + "history": [] + }, + "region": "aws-eu-central-1", + "status": "started" + }, + "ref_id": "main-apm", + "region": "aws-eu-central-1" + } + ], + "appsearch": [], + "elasticsearch": [ + { + "id": "1239f7ee7196439ba2d105319ac5eba7", + "info": { + "associated_apm_clusters": [ + { + "apm_id": "12328579b3bf40c8b58c1a0ed5a4bd8b", + "enabled": true + } + ], + "associated_appsearch_clusters": [], + "associated_enterprise_search_clusters": [], + "associated_kibana_clusters": [ + { + "enabled": true, + "kibana_id": "123dcfda06254ca789eb287e8b73ff4c" + } + ], + "cluster_id": "1239f7ee7196439ba2d105319ac5eba7", + "cluster_name": "up2d", + "deployment_id": "123365f2805e46808d40849b1c0b266b", + "elasticsearch": { + "blocking_issues": { + "cluster_level": [], + "healthy": true, + "index_level": [] + }, + "healthy": true + }, + "external_links": [], + "healthy": true, + "locked": false, + "metadata": { + "cloud_id": "up2d:someCloudID", + "endpoint": "1239f7ee7196439ba2d105319ac5eba7.eu-central-1.aws.cloud.es.io", + "last_modified": "2020-10-14T05:04:56.085Z", + "ports": { + "http": 9200, + "https": 9243, + "transport_passthrough": 9400 + }, + "version": 23 + }, + "plan_info": { + "current": { + "attempt_end_time": "2020-10-14T05:03:31.757Z", + "attempt_start_time": "2020-10-14T05:02:24.559Z", + "healthy": true, + "plan": { + "cluster_topology": [ + { + "id": "hot_content", + "elasticsearch": { + "node_attributes": { + "data": "hot" + }, + "system_settings": { + "auto_create_index": true, + "destructive_requires_name": false, + "enable_close_index": true, + "monitoring_collection_interval": -1, + "monitoring_history_duration": "3d", + "reindex_whitelist": [], + "scripting": { + "inline": { + "enabled": true + }, + "stored": { + "enabled": true + } + }, + "use_disk_threshold": true + } + }, + "instance_configuration_id": "aws.data.highio.i3", + "node_type": { + "data": true, + "ingest": true, + "master": true, + "ml": false + }, + "size": { + "resource": "memory", + "value": 8192 + }, + "topology_element_control": { + "min": { + "resource": "memory", + "value": 1024 + } + }, + "zone_count": 2 + }, + { + "elasticsearch": { + "system_settings": { + "auto_create_index": true, + "destructive_requires_name": false, + "enable_close_index": true, + "monitoring_collection_interval": -1, + "monitoring_history_duration": "3d", + "reindex_whitelist": [], + "scripting": { + "inline": { + "enabled": true + }, + "stored": { + "enabled": true + } + }, + "use_disk_threshold": true + } + }, + "instance_configuration_id": "aws.coordinating.m5d", + "node_type": { + "data": false, + "ingest": true, + "master": false, + "ml": false + }, + "size": { + "resource": "memory", + "value": 0 + }, + "zone_count": 2 + }, + { + "elasticsearch": { + "system_settings": { + "auto_create_index": true, + "destructive_requires_name": false, + "enable_close_index": true, + "monitoring_collection_interval": -1, + "monitoring_history_duration": "3d", + "reindex_whitelist": [], + "scripting": { + "inline": { + "enabled": true + }, + "stored": { + "enabled": true + } + }, + "use_disk_threshold": true + } + }, + "instance_configuration_id": "aws.master.r5d", + "node_type": { + "data": false, + "ingest": false, + "master": true, + "ml": false + }, + "size": { + "resource": "memory", + "value": 0 + }, + "zone_count": 3 + }, + { + "elasticsearch": { + "system_settings": { + "auto_create_index": true, + "destructive_requires_name": false, + "enable_close_index": true, + "monitoring_collection_interval": -1, + "monitoring_history_duration": "3d", + "reindex_whitelist": [], + "scripting": { + "inline": { + "enabled": true + }, + "stored": { + "enabled": true + } + }, + "use_disk_threshold": true + } + }, + "instance_configuration_id": "aws.ml.m5d", + "node_type": { + "data": false, + "ingest": false, + "master": false, + "ml": true + }, + "size": { + "resource": "memory", + "value": 0 + }, + "zone_count": 1 + } + ], + "deployment_template": { + "id": "aws-io-optimized-v2" + }, + "elasticsearch": { + "user_bundles": [ + { + "name": "custom-bundle", + "elasticsearch_version": "7.9.2", + "url": "http://12345" + }, + { + "name": "custom-bundle2", + "elasticsearch_version": "7.9.2", + "url": "http://123456" + } + ], + "user_plugins": [ + { + "name": "custom-plugin", + "elasticsearch_version": "7.9.2", + "url": "http://12345" + }, + { + "name": "custom-plugin2", + "elasticsearch_version": "7.9.2", + "url": "http://123456" + } + ], + "version": "7.9.2" + }, + "tiebreaker_topology": { + "memory_per_node": 1024 + } + }, + "plan_attempt_id": "42025723-f52a-40ed-b6d2-126fe6b9cabe", + "plan_attempt_log": [], + "plan_end_time": "0001-01-01T00:00:00.000Z" + }, + "healthy": true, + "history": [] + }, + "region": "aws-eu-central-1", + "status": "started", + "system_alerts": [] + }, + "ref_id": "main-elasticsearch", + "region": "aws-eu-central-1" + } + ], + "enterprise_search": [], + "kibana": [ + { + "elasticsearch_cluster_ref_id": "main-elasticsearch", + "id": "123dcfda06254ca789eb287e8b73ff4c", + "info": { + "cluster_id": "123dcfda06254ca789eb287e8b73ff4c", + "cluster_name": "up2d", + "deployment_id": "123365f2805e46808d40849b1c0b266b", + "elasticsearch_cluster": { + "elasticsearch_id": "1239f7ee7196439ba2d105319ac5eba7" + }, + "external_links": [], + "healthy": true, + "metadata": { + "endpoint": "123dcfda06254ca789eb287e8b73ff4c.eu-central-1.aws.cloud.es.io", + "last_modified": "2020-10-14T05:04:55.982Z", + "ports": { + "http": 9200, + "https": 9243, + "transport_passthrough": 9400 + }, + "version": 10 + }, + "plan_info": { + "current": { + "attempt_end_time": "2020-10-14T05:06:38.610Z", + "attempt_start_time": "2020-10-14T05:04:55.535Z", + "healthy": true, + "plan": { + "cluster_topology": [ + { + "instance_configuration_id": "aws.kibana.r5d", + "kibana": { + "system_settings": {} + }, + "size": { + "resource": "memory", + "value": 1024 + }, + "zone_count": 1 + } + ], + "kibana": { + "system_settings": {}, + "version": "7.9.2" + } + }, + "plan_attempt_id": "b414904a-5f2b-485f-9e2a-05a181443247", + "plan_attempt_log": [], + "plan_end_time": "0001-01-01T00:00:00.000Z" + }, + "healthy": true, + "history": [] + }, + "region": "aws-eu-central-1", + "status": "started" + }, + "ref_id": "main-kibana", + "region": "aws-eu-central-1" + } + ] + } +} \ No newline at end of file