From 677bcd577d1140fe18472e84f7ac7e289384bc37 Mon Sep 17 00:00:00 2001 From: Aleksandr Tuliakov Date: Thu, 1 Aug 2024 09:44:41 +0000 Subject: [PATCH] Pull request #1319: feat(opensearch): add node_group name to hosts list and change type from set to list Merge in CLOUD/terraform-provider-yandex-mirror from feat/opensearch-hosts to master Squashed commit of the following: commit a828b3ce359f7b7050c52767f5259151c0391f6f Author: Aleksandr Tuliakov Date: Tue Jul 30 21:42:21 2024 +0200 chore(opensearch): update docs commit 0bbcb7b30a3f269bb979b00d4ef9ca1670e1c34b Author: Aleksandr Tuliakov Date: Mon Jul 29 15:58:00 2024 +0200 chore(opensearch): update changelog commit dda7e944c9fc88d6703b75b72dd08a3fecc6bc4f Author: Aleksandr Tuliakov Date: Mon Jul 29 10:52:19 2024 +0200 feat(opensearch): add node_group name to hosts list and change type from set to list --- .../unreleased/FEATURES-20240729-155747.yaml | 3 + ...ource_mdb_opensearch_cluster.html.markdown | 2 + .../r/mdb_opensearch_cluster.html.markdown | 2 + .../mdb/opensearch/legacy/upgrader_from_v0.go | 12 +- .../mdb/opensearch/legacy/upgrader_from_v1.go | 343 ++++++++++++++++++ .../services/mdb/opensearch/model/models.go | 13 +- .../services/mdb/opensearch/resource.go | 6 +- .../services/mdb/opensearch/schema/schema.go | 5 +- 8 files changed, 374 insertions(+), 12 deletions(-) create mode 100644 .changes/unreleased/FEATURES-20240729-155747.yaml create mode 100644 yandex-framework/services/mdb/opensearch/legacy/upgrader_from_v1.go diff --git a/.changes/unreleased/FEATURES-20240729-155747.yaml b/.changes/unreleased/FEATURES-20240729-155747.yaml new file mode 100644 index 00000000..d4eab382 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240729-155747.yaml @@ -0,0 +1,3 @@ +kind: FEATURES +body: 'opensearch: add `node_group` name to `hosts` list' +time: 2024-07-29T15:57:47.159745+02:00 diff --git a/website/docs/d/datasource_mdb_opensearch_cluster.html.markdown b/website/docs/d/datasource_mdb_opensearch_cluster.html.markdown index d9f8c18e..e8ee6e6d 100644 --- a/website/docs/d/datasource_mdb_opensearch_cluster.html.markdown +++ b/website/docs/d/datasource_mdb_opensearch_cluster.html.markdown @@ -119,6 +119,8 @@ The `hosts` block supports: * `assign_public_ip` - Sets whether the host should get a public IP address. Can be either `true` or `false`. +* `node_group` - Name of the node group. + The `maintenance_window` block supports: * `type` - Type of a maintenance window. Can be either `ANYTIME` or `WEEKLY`. A day and hour need to be specified with the weekly window. diff --git a/website/docs/r/mdb_opensearch_cluster.html.markdown b/website/docs/r/mdb_opensearch_cluster.html.markdown index f892eb04..7e6d2b5a 100644 --- a/website/docs/r/mdb_opensearch_cluster.html.markdown +++ b/website/docs/r/mdb_opensearch_cluster.html.markdown @@ -282,6 +282,8 @@ The `hosts` block supports: * `assign_public_ip` - Sets whether the host should get a public IP address. Can be either `true` or `false`. +* `node_group` - Name of the node group. + ## Import A cluster can be imported using the `id` of the resource, e.g. diff --git a/yandex-framework/services/mdb/opensearch/legacy/upgrader_from_v0.go b/yandex-framework/services/mdb/opensearch/legacy/upgrader_from_v0.go index 352d9242..fbed64b4 100644 --- a/yandex-framework/services/mdb/opensearch/legacy/upgrader_from_v0.go +++ b/yandex-framework/services/mdb/opensearch/legacy/upgrader_from_v0.go @@ -77,7 +77,7 @@ type dashboardNode struct { AssignPublicIP types.Bool `tfsdk:"assign_public_ip"` } -// return StateUpgrader implementation from 0 (prior state version) to 1 (Schema.Version) +// return StateUpgrader implementation from 0 (prior state version) to 2 (Schema.Version) func NewUpgraderFromV0(ctx context.Context) resource.StateUpgrader { return resource.StateUpgrader{ PriorSchema: &schema.Schema{ @@ -266,7 +266,7 @@ func NewUpgraderFromV0(ctx context.Context) resource.StateUpgrader { }, Description: "Deployment environment of the OpenSearch cluster.", }, - "hosts": common_schema.Hosts(), + "hosts": hostsWithoutNodeGroup(), "network_id": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -430,6 +430,12 @@ func NewUpgraderFromV0(ctx context.Context) resource.StateUpgrader { return } + newHosts, diags := transformHosts(ctx, oldModel.Hosts) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + newModel := model.OpenSearch{ ID: oldModel.ID, ClusterID: oldModel.ID, @@ -439,7 +445,7 @@ func NewUpgraderFromV0(ctx context.Context) resource.StateUpgrader { Labels: oldModel.Labels, Environment: oldModel.Environment, Config: newConfigObj, - Hosts: oldModel.Hosts, + Hosts: newHosts, NetworkID: oldModel.NetworkID, Health: oldModel.Health, Status: oldModel.Status, diff --git a/yandex-framework/services/mdb/opensearch/legacy/upgrader_from_v1.go b/yandex-framework/services/mdb/opensearch/legacy/upgrader_from_v1.go new file mode 100644 index 00000000..5039f03e --- /dev/null +++ b/yandex-framework/services/mdb/opensearch/legacy/upgrader_from_v1.go @@ -0,0 +1,343 @@ +package legacy + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/yandex-cloud/terraform-provider-yandex/yandex-framework/services/mdb/opensearch/model" + common_schema "github.com/yandex-cloud/terraform-provider-yandex/yandex-framework/services/mdb/opensearch/schema" + "github.com/yandex-cloud/terraform-provider-yandex/yandex-framework/services/mdb/opensearch/validate" +) + +type openSearchV1 struct { + Timeouts timeouts.Value `tfsdk:"timeouts"` + ID types.String `tfsdk:"id"` + ClusterID types.String `tfsdk:"cluster_id"` + FolderID types.String `tfsdk:"folder_id"` + CreatedAt types.String `tfsdk:"created_at"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Labels types.Map `tfsdk:"labels"` + Environment types.String `tfsdk:"environment"` + Config types.Object `tfsdk:"config"` + Hosts types.Set `tfsdk:"hosts"` + NetworkID types.String `tfsdk:"network_id"` + Health types.String `tfsdk:"health"` + Status types.String `tfsdk:"status"` + SecurityGroupIDs types.Set `tfsdk:"security_group_ids"` + ServiceAccountID types.String `tfsdk:"service_account_id"` + DeletionProtection types.Bool `tfsdk:"deletion_protection"` + MaintenanceWindow types.Object `tfsdk:"maintenance_window"` +} + +type hostWithoutNodeGroup struct { + FQDN types.String `tfsdk:"fqdn"` + Type types.String `tfsdk:"type"` + Roles types.Set `tfsdk:"roles"` + AssignPublicIP types.Bool `tfsdk:"assign_public_ip"` + Zone types.String `tfsdk:"zone"` + SubnetID types.String `tfsdk:"subnet_id"` +} + +func hostsWithoutNodeGroup() schema.SetNestedAttribute { + return schema.SetNestedAttribute{ + Computed: true, + Description: "Current nodes in the cluster", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "fqdn": schema.StringAttribute{Computed: true}, + "zone": schema.StringAttribute{Computed: true}, + "type": schema.StringAttribute{Computed: true}, + "roles": schema.SetAttribute{ + Computed: true, + ElementType: types.StringType, + }, + "assign_public_ip": schema.BoolAttribute{Computed: true, Optional: true}, + "subnet_id": schema.StringAttribute{Computed: true, Optional: true}, + }, + }, + } +} + +func transformHosts(ctx context.Context, hostsAttr basetypes.SetValue) (basetypes.ListValue, diag.Diagnostics) { + current := make([]hostWithoutNodeGroup, 0, len(hostsAttr.Elements())) + diags := hostsAttr.ElementsAs(ctx, ¤t, false) + if diags.HasError() { + return types.ListUnknown(model.HostType), diags + } + + target := make([]model.Host, len(current)) + for i := range current { + target[i] = model.Host{ + FQDN: current[i].FQDN, + Type: current[i].Type, + Roles: current[i].Roles, + AssignPublicIP: current[i].AssignPublicIP, + Zone: current[i].Zone, + SubnetID: current[i].SubnetID, + NodeGroup: types.StringNull(), + } + } + + return types.ListValueFrom(ctx, model.HostType, target) +} + +// return StateUpgrader implementation from 1 (prior state version) to 2 (Schema.Version) +func NewUpgraderFromV1(ctx context.Context) resource.StateUpgrader { + return resource.StateUpgrader{ + PriorSchema: &schema.Schema{ + Version: 1, + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + "config": schema.SingleNestedBlock{ + Description: "Configuration of the OpenSearch cluster.", + Blocks: map[string]schema.Block{ + "opensearch": schema.SingleNestedBlock{ + Validators: []validator.Object{ + objectvalidator.IsRequired(), + }, + Attributes: map[string]schema.Attribute{ + "plugins": schema.SetAttribute{ + Computed: true, + Optional: true, + ElementType: types.StringType, + }, + }, + Blocks: map[string]schema.Block{ + //NOTE: changed "set" to "list+customValidator" because https://github.com/hashicorp/terraform-plugin-sdk/issues/1210 + "node_groups": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.IsRequired(), + listvalidator.SizeAtLeast(1), + validate.UniqueByField("name", func(x model.OpenSearchNode) string { return x.Name.ValueString() }), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "resources": common_schema.NodeResource(), + }, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{Required: true}, + "hosts_count": schema.Int64Attribute{Required: true}, + "zone_ids": schema.SetAttribute{ + Required: true, + ElementType: types.StringType, + }, + "subnet_ids": schema.SetAttribute{ + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + + "assign_public_ip": schema.BoolAttribute{Computed: true, Optional: true}, + "roles": schema.SetAttribute{ + Optional: true, + Computed: true, + ElementType: types.StringType, + Validators: []validator.Set{ + validate.UniqueCaseInsensitive(), + }, + }, + }, + }, + }, + }, + }, + "dashboards": schema.SingleNestedBlock{ + Validators: []validator.Object{ + objectvalidator.AlsoRequires(path.Expressions{ + path.MatchRoot("config").AtName("dashboards").AtName("node_groups"), + }...), + }, + Blocks: map[string]schema.Block{ + //NOTE: changed "set" to "list+customValidator" because https://github.com/hashicorp/terraform-plugin-sdk/issues/1210 + "node_groups": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + validate.UniqueByField("name", func(x model.DashboardNode) string { return x.Name.ValueString() }), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "resources": common_schema.NodeResource(), + }, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{Required: true}, + "hosts_count": schema.Int64Attribute{Required: true}, + "zone_ids": schema.SetAttribute{ + Required: true, + ElementType: types.StringType, + }, + "subnet_ids": schema.SetAttribute{ + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + + "assign_public_ip": schema.BoolAttribute{Computed: true, Optional: true}, + }, + }, + }, + }, + }, + "access": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "data_transfer": schema.BoolAttribute{Optional: true}, + "serverless": schema.BoolAttribute{Optional: true}, + }, + }, + }, + Attributes: map[string]schema.Attribute{ + "version": schema.StringAttribute{Computed: true, Optional: true}, + "admin_password": schema.StringAttribute{ + Required: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + "maintenance_window": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("ANYTIME", "WEEKLY"), + }, + }, + "day": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"), + }, + }, + "hour": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 24), + }, + }, + }, + }, + }, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "cluster_id": schema.StringAttribute{ + Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "folder_id": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "ID of the folder that the OpenSearch cluster belongs to.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + stringplanmodifier.RequiresReplace(), + }, + }, + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{Required: true, Description: "Name of the OpenSearch cluster. The name must be unique within the folder."}, + "description": schema.StringAttribute{Optional: true, Description: "Description of the OpenSearch cluster"}, + "labels": schema.MapAttribute{ + Optional: true, + ElementType: types.StringType, + Description: "Custom labels for the OpenSearch cluster as `key:value` pairs.", + }, + "environment": schema.StringAttribute{ + Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Deployment environment of the OpenSearch cluster.", + }, + "hosts": hostsWithoutNodeGroup(), + "network_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "ID of the network that the cluster belongs to.", + }, + "health": schema.StringAttribute{Computed: true, Description: "Aggregated cluster health."}, + "status": schema.StringAttribute{Computed: true, Description: "Current state of the cluster."}, + "security_group_ids": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + Description: "User security groups", + }, + "service_account_id": schema.StringAttribute{Optional: true}, + "deletion_protection": schema.BoolAttribute{Computed: true, Optional: true}, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + oldModel := openSearchV1{} + resp.Diagnostics.Append(req.State.Get(ctx, &oldModel)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("UpgraderFromV1.OldModel: %+v\n", oldModel)) + + newHosts, diags := transformHosts(ctx, oldModel.Hosts) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + newModel := model.OpenSearch{ + ID: oldModel.ID, + ClusterID: oldModel.ID, + FolderID: oldModel.FolderID, + CreatedAt: oldModel.CreatedAt, + Name: oldModel.Name, + Labels: oldModel.Labels, + Environment: oldModel.Environment, + Config: oldModel.Config, + Hosts: newHosts, + NetworkID: oldModel.NetworkID, + Health: oldModel.Health, + Status: oldModel.Status, + SecurityGroupIDs: oldModel.SecurityGroupIDs, + ServiceAccountID: oldModel.ServiceAccountID, + DeletionProtection: oldModel.DeletionProtection, + MaintenanceWindow: oldModel.MaintenanceWindow, + Timeouts: oldModel.Timeouts, + } + + resp.Diagnostics.Append(resp.State.Set(ctx, newModel)...) + }, + } +} diff --git a/yandex-framework/services/mdb/opensearch/model/models.go b/yandex-framework/services/mdb/opensearch/model/models.go index 3fa57736..72472b16 100644 --- a/yandex-framework/services/mdb/opensearch/model/models.go +++ b/yandex-framework/services/mdb/opensearch/model/models.go @@ -27,7 +27,7 @@ type OpenSearch struct { Labels types.Map `tfsdk:"labels"` Environment types.String `tfsdk:"environment"` Config types.Object `tfsdk:"config"` - Hosts types.Set `tfsdk:"hosts"` + Hosts types.List `tfsdk:"hosts"` NetworkID types.String `tfsdk:"network_id"` Health types.String `tfsdk:"health"` Status types.String `tfsdk:"status"` @@ -99,6 +99,7 @@ type Host struct { AssignPublicIP types.Bool `tfsdk:"assign_public_ip"` Zone types.String `tfsdk:"zone"` SubnetID types.String `tfsdk:"subnet_id"` + NodeGroup types.String `tfsdk:"node_group"` } type MaintenanceWindow struct { @@ -158,7 +159,7 @@ var NodeResourceAttrTypes = map[string]attr.Type{ "disk_type_id": types.StringType, } -var hostType = types.ObjectType{ +var HostType = types.ObjectType{ AttrTypes: map[string]attr.Type{ "fqdn": types.StringType, "type": types.StringType, @@ -166,6 +167,7 @@ var hostType = types.ObjectType{ "assign_public_ip": types.BoolType, "zone": types.StringType, "subnet_id": types.StringType, + "node_group": types.StringType, }, } @@ -552,14 +554,14 @@ func nullableStringSliceToSet(ctx context.Context, s []string) (types.Set, diag. return types.SetValueFrom(ctx, types.StringType, s) } -func HostsToState(ctx context.Context, hosts []*opensearch.Host) (types.Set, diag.Diagnostics) { +func HostsToState(ctx context.Context, hosts []*opensearch.Host) (types.List, diag.Diagnostics) { res := make([]Host, 0, len(hosts)) for _, h := range hosts { roles, diags := rolesToSet(h.GetRoles()) if diags.HasError() { diags.AddError("Failed to parse hosts.roles", fmt.Sprintf("Error while parsing roles for host: %s", h.GetName())) - return types.SetUnknown(hostType), diags + return types.ListUnknown(HostType), diags } res = append(res, Host{ @@ -569,10 +571,11 @@ func HostsToState(ctx context.Context, hosts []*opensearch.Host) (types.Set, dia AssignPublicIP: types.BoolValue(h.GetAssignPublicIp()), Zone: types.StringValue(h.GetZoneId()), SubnetID: types.StringValue(h.GetSubnetId()), + NodeGroup: types.StringValue(h.GetNodeGroup()), }) } - return types.SetValueFrom(ctx, hostType, res) + return types.ListValueFrom(ctx, HostType, res) } func ParseConfig(ctx context.Context, state *OpenSearch) (*Config, diag.Diagnostics) { diff --git a/yandex-framework/services/mdb/opensearch/resource.go b/yandex-framework/services/mdb/opensearch/resource.go index 382e2e3e..36c70ca3 100644 --- a/yandex-framework/services/mdb/opensearch/resource.go +++ b/yandex-framework/services/mdb/opensearch/resource.go @@ -80,8 +80,10 @@ func (o *openSearchClusterResource) ImportState(ctx context.Context, req resourc func (o *openSearchClusterResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { return map[int64]resource.StateUpgrader{ - // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + // State upgrade implementation from 0 to 2 (Schema.Version) 0: legacy.NewUpgraderFromV0(ctx), + // State upgrade implementation from 1 (prior state version) to 2 (Schema.Version) + 1: legacy.NewUpgraderFromV1(ctx), } } @@ -366,7 +368,7 @@ func (o *openSearchClusterResource) processDashboardsNodeGroupsUpdate(ctx contex func (o *openSearchClusterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { tflog.Info(ctx, "Initializing opensearch data source schema") resp.Schema = schema.Schema{ - Version: 1, + Version: 2, Blocks: map[string]schema.Block{ "timeouts": timeouts.Block(ctx, timeouts.Opts{ Create: true, diff --git a/yandex-framework/services/mdb/opensearch/schema/schema.go b/yandex-framework/services/mdb/opensearch/schema/schema.go index 34e705b5..ff5e0b4e 100644 --- a/yandex-framework/services/mdb/opensearch/schema/schema.go +++ b/yandex-framework/services/mdb/opensearch/schema/schema.go @@ -24,8 +24,8 @@ func NodeResourceAttributes() map[string]schema.Attribute { } } -func Hosts() schema.SetNestedAttribute { - return schema.SetNestedAttribute{ +func Hosts() schema.ListNestedAttribute { + return schema.ListNestedAttribute{ Computed: true, Description: "Current nodes in the cluster", NestedObject: schema.NestedAttributeObject{ @@ -39,6 +39,7 @@ func Hosts() schema.SetNestedAttribute { }, "assign_public_ip": schema.BoolAttribute{Computed: true, Optional: true}, "subnet_id": schema.StringAttribute{Computed: true, Optional: true}, + "node_group": schema.StringAttribute{Computed: true}, }, }, }