From d5814bfdad5f0945ff7db71d2d5da1bbae6d67e1 Mon Sep 17 00:00:00 2001 From: Pascal Hofmann Date: Fri, 14 Apr 2023 08:10:54 +0200 Subject: [PATCH] ec_deployment: Add snapshot settings --- docs/resources/ec_deployment.md | 20 +++ .../elasticsearch/v2/elasticsearch_payload.go | 4 + .../v2/elasticsearch_payload_test.go | 82 ++++++++++- .../elasticsearch/v2/elasticsearch_read.go | 7 + .../v2/elasticsearch_read_test.go | 130 ++++++++++++++++++ .../v2/elasticsearch_snapshot.go | 90 ++++++++++++ .../elasticsearch/v2/schema.go | 52 +++++++ 7 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_snapshot.go diff --git a/docs/resources/ec_deployment.md b/docs/resources/ec_deployment.md index ef2a9e377..af8ff8abb 100644 --- a/docs/resources/ec_deployment.md +++ b/docs/resources/ec_deployment.md @@ -308,6 +308,7 @@ The required `elasticsearch` block supports the following arguments: * `ref_id` - (Optional) Can be set on the Elasticsearch resource. The default value `main-elasticsearch` is recommended. * `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` (Elastic Cloud Enterprise only, Optional) snapshot configuration settings for an Elasticsearch cluster. * `snapshot_source` (Optional) Restores data from a snapshot of another deployment. * `extension` (Optional) Custom Elasticsearch bundles or plugins. Can be set multiple times. * `autoscale` (Optional) Enable or disable autoscaling. Defaults to the setting coming from the deployment template. Accepted values are `"true"` or `"false"`. @@ -367,6 +368,25 @@ The optional `elasticsearch.remote_cluster` block can be set multiple times. It * `ref_id` (Optional) Remote Elasticsearch `ref_id`. The default value `main-elasticsearch` is recommended. * `skip_unavailable` (Optional) If true, skip the cluster during search when disconnected. Defaults to `false`. +##### Snapshot + +~> **This setting can only be used with Elastic Cloud Enterprise** For Elastic Cloud SaaS please use the [elasticstack_elasticsearch_snapshot_repository](https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/elasticsearch_snapshot_repository) resource from the [Elastic Stack terraform provider](https://registry.terraform.io/providers/elastic/elasticstack/latest). + +* `enabled` (Required) Indicates if Snapshotting is enabled. +* `repository` (Optional) Snapshot repository configuration. + +##### Repository + +~> **This setting can only be used with Elastic Cloud Enterprise** For Elastic Cloud SaaS please use the [elasticstack_elasticsearch_snapshot_repository](https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/elasticsearch_snapshot_repository) resource from the [Elastic Stack terraform provider](https://registry.terraform.io/providers/elastic/elasticstack/latest). + +- `reference` (Optional) Cluster snapshot reference repository settings, containing the repository name in ECE fashion. + +##### Reference + +~> **This setting can only be used with Elastic Cloud Enterprise** For Elastic Cloud SaaS please use the [elasticstack_elasticsearch_snapshot_repository](https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/elasticsearch_snapshot_repository) resource from the [Elastic Stack terraform provider](https://registry.terraform.io/providers/elastic/elasticstack/latest). + +- `repository_name` (Optional) ECE snapshot repository name, from the '/platform/configuration/snapshots/repositories' endpoint. + ##### Snapshot source The optional `elasticsearch.snapshot_source` block, which restores data from a snapshot of another deployment, supports the following arguments: diff --git a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_payload.go b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_payload.go index 2aa98d95c..fe28a0e31 100644 --- a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_payload.go +++ b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_payload.go @@ -46,6 +46,7 @@ type ElasticsearchTF struct { MlTier types.Object `tfsdk:"ml"` Config types.Object `tfsdk:"config"` RemoteCluster types.Set `tfsdk:"remote_cluster"` + Snapshot types.Object `tfsdk:"snapshot"` SnapshotSource types.Object `tfsdk:"snapshot_source"` Extension types.Set `tfsdk:"extension"` TrustAccount types.Set `tfsdk:"trust_account"` @@ -106,6 +107,9 @@ func (es *ElasticsearchTF) payload(ctx context.Context, res *models.Elasticsearc res.Plan.Elasticsearch, ds = elasticsearchConfigPayload(ctx, es.Config, res.Plan.Elasticsearch) diags.Append(ds...) + res.Settings, ds = elasticsearchSnapshotPayload(ctx, es.Snapshot, res.Settings) + diags.Append(ds...) + diags.Append(elasticsearchSnapshotSourcePayload(ctx, es.SnapshotSource, res.Plan)...) diags.Append(elasticsearchExtensionPayload(ctx, es.Extension, res.Plan.Elasticsearch)...) diff --git a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_payload_test.go b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_payload_test.go index c2d038cec..1593db3a5 100644 --- a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_payload_test.go +++ b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_payload_test.go @@ -1560,7 +1560,7 @@ func Test_writeElasticsearch(t *testing.T) { }), }, { - name: "parses an ES resource with snapshot settings", + name: "parses an ES resource with snapshot source settings", args: args{ es: Elasticsearch{ RefId: ec.String("main-elasticsearch"), @@ -1633,6 +1633,86 @@ func Test_writeElasticsearch(t *testing.T) { }, }), }, + { + name: "parses an ES resource with snapshot settings", + args: args{ + es: Elasticsearch{ + RefId: ec.String("main-elasticsearch"), + ResourceId: ec.String(mock.ValidClusterID), + Region: ec.String("some-region"), + Snapshot: &ElasticsearchSnapshot{ + Enabled: true, + Repository: &ElasticsearchSnapshotRepositoryInfo{ + Reference: &ElasticsearchSnapshotRepositoryReference{ + RepositoryName: "my-snapshot-repository", + }, + }, + }, + HotTier: &ElasticsearchTopology{ + id: "hot_content", + Size: ec.String("2g"), + ZoneCount: 1, + }, + }, + template: testutil.ParseDeploymentTemplate(t, "../../testdata/template-aws-io-optimized-v2.json"), + templateID: "aws-io-optimized-v2", + version: "7.7.0", + useNodeRoles: false, + }, + want: EnrichWithEmptyTopologies(tp770(), &models.ElasticsearchPayload{ + Region: ec.String("some-region"), + RefID: ec.String("main-elasticsearch"), + Settings: &models.ElasticsearchClusterSettings{ + DedicatedMastersThreshold: 6, + Snapshot: &models.ClusterSnapshotSettings{ + Enabled: ec.Bool(true), + Repository: &models.ClusterSnapshotRepositoryInfo{ + Reference: &models.ClusterSnapshotRepositoryReference{ + RepositoryName: "my-snapshot-repository", + }, + }, + }, + }, + Plan: &models.ElasticsearchClusterPlan{ + AutoscalingEnabled: ec.Bool(false), + Elasticsearch: &models.ElasticsearchConfiguration{ + Version: "7.7.0", + }, + DeploymentTemplate: &models.DeploymentTemplateReference{ + ID: ec.String("aws-io-optimized-v2"), + }, + ClusterTopology: []*models.ElasticsearchClusterTopologyElement{ + { + ID: "hot_content", + ZoneCount: 1, + InstanceConfigurationID: "aws.data.highio.i3", + Size: &models.TopologySize{ + Resource: ec.String("memory"), + Value: ec.Int32(2048), + }, + NodeType: &models.ElasticsearchNodeType{ + Data: ec.Bool(true), + Ingest: ec.Bool(true), + Master: ec.Bool(true), + }, + Elasticsearch: &models.ElasticsearchConfiguration{ + NodeAttributes: map[string]string{"data": "hot"}, + }, + TopologyElementControl: &models.TopologyElementControl{ + Min: &models.TopologySize{ + Resource: ec.String("memory"), + Value: ec.Int32(1024), + }, + }, + AutoscalingMax: &models.TopologySize{ + Value: ec.Int32(118784), + Resource: ec.String("memory"), + }, + }, + }, + }, + }), + }, { name: "parse autodetect configuration strategy", args: args{ diff --git a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_read.go b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_read.go index 7ae4f8b14..39ffd6772 100644 --- a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_read.go +++ b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_read.go @@ -41,6 +41,7 @@ type Elasticsearch struct { MlTier *ElasticsearchTopology `tfsdk:"ml"` Config *ElasticsearchConfig `tfsdk:"config"` RemoteCluster ElasticsearchRemoteClusters `tfsdk:"remote_cluster"` + Snapshot *ElasticsearchSnapshot `tfsdk:"snapshot"` SnapshotSource *ElasticsearchSnapshotSource `tfsdk:"snapshot_source"` Extension ElasticsearchExtensions `tfsdk:"extension"` TrustAccount ElasticsearchTrustAccounts `tfsdk:"trust_account"` @@ -118,6 +119,12 @@ func readElasticsearch(in *models.ElasticsearchResourceInfo, remotes *models.Rem } es.Extension = extensions + snapshot, err := readElasticsearchSnapshot(in.Info.Settings) + if err != nil { + return nil, err + } + es.Snapshot = snapshot + accounts, err := readElasticsearchTrustAccounts(in.Info.Settings) if err != nil { return nil, err diff --git a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_read_test.go b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_read_test.go index c3e9965e2..b0a6270ff 100644 --- a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_read_test.go +++ b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_read_test.go @@ -250,6 +250,136 @@ func Test_readElasticsearch(t *testing.T) { }, }, }, + { + name: "parses an elasticsearch resource with snapshot repository", + args: args{in: []*models.ElasticsearchResourceInfo{ + { + Region: ec.String("some-region"), + RefID: ec.String("main-elasticsearch"), + Info: &models.ElasticsearchClusterInfo{ + ClusterID: &mock.ValidClusterID, + Region: "some-region", + Status: ec.String("started"), + Metadata: &models.ClusterMetadataInfo{ + CloudID: "some CLOUD ID", + Endpoint: "somecluster.cloud.elastic.co", + Ports: &models.ClusterMetadataPortInfo{ + HTTP: ec.Int32(9200), + HTTPS: ec.Int32(9243), + }, + }, + Settings: &models.ElasticsearchClusterSettings{ + Snapshot: &models.ClusterSnapshotSettings{ + Enabled: ec.Bool(true), + Repository: &models.ClusterSnapshotRepositoryInfo{ + Reference: &models.ClusterSnapshotRepositoryReference{ + RepositoryName: "my-snapshot-repository", + }, + }, + }, + }, + PlanInfo: &models.ElasticsearchClusterPlansInfo{ + Current: &models.ElasticsearchClusterPlanInfo{ + Plan: &models.ElasticsearchClusterPlan{ + Elasticsearch: &models.ElasticsearchConfiguration{ + Version: "7.7.0", + }, + ClusterTopology: []*models.ElasticsearchClusterTopologyElement{ + { + ID: "hot_content", + ZoneCount: 1, + InstanceConfigurationID: "aws.data.highio.i3", + Size: &models.TopologySize{ + Resource: ec.String("memory"), + Value: ec.Int32(2048), + }, + NodeType: &models.ElasticsearchNodeType{ + Data: ec.Bool(true), + Ingest: ec.Bool(true), + Master: ec.Bool(true), + Ml: ec.Bool(false), + }, + }, + }, + }, + }, + }, + }, + }, + { + Region: ec.String("some-region"), + RefID: ec.String("main-elasticsearch"), + Info: &models.ElasticsearchClusterInfo{ + ClusterID: &mock.ValidClusterID, + Region: "some-region", + Status: ec.String("stopped"), + Metadata: &models.ClusterMetadataInfo{ + CloudID: "some CLOUD ID", + Endpoint: "somecluster.cloud.elastic.co", + Ports: &models.ClusterMetadataPortInfo{ + HTTP: ec.Int32(9200), + HTTPS: ec.Int32(9243), + }, + }, + PlanInfo: &models.ElasticsearchClusterPlansInfo{ + Current: &models.ElasticsearchClusterPlanInfo{ + Plan: &models.ElasticsearchClusterPlan{ + Elasticsearch: &models.ElasticsearchConfiguration{ + Version: "7.7.0", + }, + ClusterTopology: []*models.ElasticsearchClusterTopologyElement{ + { + ID: "hot_content", + ZoneCount: 1, + InstanceConfigurationID: "aws.data.highio.i3", + Size: &models.TopologySize{ + Resource: ec.String("memory"), + Value: ec.Int32(2048), + }, + NodeType: &models.ElasticsearchNodeType{ + Data: ec.Bool(true), + Ingest: ec.Bool(true), + Master: ec.Bool(true), + Ml: ec.Bool(false), + }, + }, + }, + }, + }, + }, + }, + }, + }}, + want: &Elasticsearch{ + RefId: ec.String("main-elasticsearch"), + ResourceId: ec.String(mock.ValidClusterID), + Region: ec.String("some-region"), + CloudID: ec.String("some CLOUD ID"), + HttpEndpoint: ec.String("http://somecluster.cloud.elastic.co:9200"), + HttpsEndpoint: ec.String("https://somecluster.cloud.elastic.co:9243"), + Config: &ElasticsearchConfig{}, + HotTier: &ElasticsearchTopology{ + id: "hot_content", + InstanceConfigurationId: ec.String("aws.data.highio.i3"), + Size: ec.String("2g"), + SizeResource: ec.String("memory"), + NodeTypeData: ec.String("true"), + NodeTypeIngest: ec.String("true"), + NodeTypeMaster: ec.String("true"), + NodeTypeMl: ec.String("false"), + ZoneCount: 1, + Autoscaling: &ElasticsearchTopologyAutoscaling{}, + }, + Snapshot: &ElasticsearchSnapshot{ + Enabled: true, + Repository: &ElasticsearchSnapshotRepositoryInfo{ + Reference: &ElasticsearchSnapshotRepositoryReference{ + RepositoryName: "my-snapshot-repository", + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_snapshot.go b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_snapshot.go new file mode 100644 index 000000000..9cc506f3a --- /dev/null +++ b/ec/ecresource/deploymentresource/elasticsearch/v2/elasticsearch_snapshot.go @@ -0,0 +1,90 @@ +// 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 v2 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + + "github.com/elastic/cloud-sdk-go/pkg/models" +) + +type ElasticsearchSnapshot struct { + Enabled bool `tfsdk:"enabled"` + Repository *ElasticsearchSnapshotRepositoryInfo `tfsdk:"repository"` +} + +type ElasticsearchSnapshotRepositoryInfo struct { + Reference *ElasticsearchSnapshotRepositoryReference `tfsdk:"reference"` +} + +type ElasticsearchSnapshotRepositoryReference struct { + RepositoryName string `tfsdk:"repository_name"` +} + +func readElasticsearchSnapshot(in *models.ElasticsearchClusterSettings) (*ElasticsearchSnapshot, error) { + if in == nil || in.Snapshot == nil { + return nil, nil + } + + var snapshot ElasticsearchSnapshot + + if in.Snapshot.Enabled != nil { + snapshot.Enabled = *in.Snapshot.Enabled + } + if in.Snapshot.Repository != nil { + snapshot.Repository = &ElasticsearchSnapshotRepositoryInfo{} + if in.Snapshot.Repository.Reference != nil { + snapshot.Repository.Reference = &ElasticsearchSnapshotRepositoryReference{ + RepositoryName: in.Snapshot.Repository.Reference.RepositoryName, + } + } + } + + return &snapshot, nil +} + +func elasticsearchSnapshotPayload(ctx context.Context, srcObj attr.Value, model *models.ElasticsearchClusterSettings) (*models.ElasticsearchClusterSettings, diag.Diagnostics) { + var snapshot *ElasticsearchSnapshot + if srcObj.IsNull() || srcObj.IsUnknown() { + return model, nil + } + + if diags := tfsdk.ValueAs(ctx, srcObj, &snapshot); diags.HasError() { + return model, diags + } + + if model == nil { + model = &models.ElasticsearchClusterSettings{} + } + model.Snapshot = &models.ClusterSnapshotSettings{ + Enabled: &snapshot.Enabled, + Repository: &models.ClusterSnapshotRepositoryInfo{}, + } + + if snapshot.Repository != nil && snapshot.Repository.Reference != nil { + model.Snapshot.Repository.Reference = &models.ClusterSnapshotRepositoryReference{ + RepositoryName: snapshot.Repository.Reference.RepositoryName, + } + } + + return model, nil +} diff --git a/ec/ecresource/deploymentresource/elasticsearch/v2/schema.go b/ec/ecresource/deploymentresource/elasticsearch/v2/schema.go index 9ee18c0ec..371847e76 100644 --- a/ec/ecresource/deploymentresource/elasticsearch/v2/schema.go +++ b/ec/ecresource/deploymentresource/elasticsearch/v2/schema.go @@ -112,6 +112,8 @@ func ElasticsearchSchema() tfsdk.Attribute { "remote_cluster": ElasticsearchRemoteClusterSchema(), + "snapshot": elasticsearchSnapshotSchema(), + "snapshot_source": elasticsearchSnapshotSourceSchema(), "extension": elasticsearchExtensionSchema(), @@ -260,6 +262,56 @@ func ElasticsearchRemoteClusterSchema() tfsdk.Attribute { } } +func elasticsearchSnapshotSchema() tfsdk.Attribute { + return tfsdk.Attribute{ + Description: "(Elastic Cloud Enterprise only) Optional snapshot configuration settings for an Elasticsearch cluster.", + Optional: true, + Computed: true, + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "enabled": { + Description: "Indicates if Snapshotting is enabled.", + Type: types.BoolType, + Required: true, + }, + "repository": elasticsearchSnapshotRepositorySchema(), + }), + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, + } +} + +func elasticsearchSnapshotRepositorySchema() tfsdk.Attribute { + return tfsdk.Attribute{ + Description: "Snapshot repository configuration", + Optional: true, + Computed: true, + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "reference": elasticsearchSnapshotRepositoryReferenceSchema(), + }), + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, + } +} + +func elasticsearchSnapshotRepositoryReferenceSchema() tfsdk.Attribute { + return tfsdk.Attribute{ + Description: "Cluster snapshot reference repository settings, containing the repository name in ECE fashion", + Optional: true, + Computed: true, + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "repository_name": { + Description: "ECE snapshot repository name, from the '/platform/configuration/snapshots/repositories' endpoint", + Type: types.StringType, + Required: true, + }}), + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, + } +} + func elasticsearchSnapshotSourceSchema() tfsdk.Attribute { return tfsdk.Attribute{ Description: "Optional snapshot source settings. Restore data from a snapshot of another deployment.",