From 78793f49ff1312738c36730a1d7a91ac5cc67718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Tue, 2 Jul 2024 09:30:47 +0100 Subject: [PATCH] Extend OpenSearch instance parameters (#443) * Extend OpenSearch instance parameters * Fix bug in parameter modelling * Improve monitoring_instance_id field documentation * Fix unit test --- docs/data-sources/opensearch_instance.md | 17 +- docs/resources/opensearch_instance.md | 17 +- .../opensearch/instance/datasource.go | 76 ++++++- .../services/opensearch/instance/resource.go | 207 +++++++++++++++--- .../opensearch/instance/resource_test.go | 190 ++++++++++------ .../opensearch/opensearch_acc_test.go | 113 ++++++---- 6 files changed, 489 insertions(+), 131 deletions(-) diff --git a/docs/data-sources/opensearch_instance.md b/docs/data-sources/opensearch_instance.md index d4db3a6d..782b4859 100644 --- a/docs/data-sources/opensearch_instance.md +++ b/docs/data-sources/opensearch_instance.md @@ -20,6 +20,7 @@ data "stackit_opensearch_instance" "example" { ``` + ## Schema ### Required @@ -42,8 +43,22 @@ data "stackit_opensearch_instance" "example" { - `version` (String) The service version. + ### Nested Schema for `parameters` Read-Only: -- `sgw_acl` (String) +- `enable_monitoring` (Boolean) Enable monitoring. +- `graphite` (String) If set, monitoring with Graphite will be enabled. Expects the host and port where the Graphite metrics should be sent to (host:port). +- `java_garbage_collector` (String) The garbage collector to use for OpenSearch. +- `java_heapspace` (Number) The amount of memory (in MB) allocated as heap by the JVM for OpenSearch. +- `java_maxmetaspace` (Number) The amount of memory (in MB) used by the JVM to store metadata for OpenSearch. +- `max_disk_threshold` (Number) The maximum disk threshold in MB. If the disk usage exceeds this threshold, the instance will be stopped. +- `metrics_frequency` (Number) The frequency in seconds at which metrics are emitted (in seconds). +- `metrics_prefix` (String) The prefix for the metrics. Could be useful when using Graphite monitoring to prefix the metrics with a certain value, like an API key. +- `monitoring_instance_id` (String) The ID of the STACKIT monitoring instance. +- `plugins` (List of String) List of plugins to install. Must be a supported plugin name. The plugins `repository-s3` and `repository-azure` are enabled by default and cannot be disabled. +- `sgw_acl` (String) Comma separated list of IP networks in CIDR notation which are allowed to access this instance. +- `syslog` (List of String) List of syslog servers to send logs to. +- `tls_ciphers` (List of String) List of TLS ciphers to use. +- `tls_protocols` (String) The TLS protocol to use. diff --git a/docs/resources/opensearch_instance.md b/docs/resources/opensearch_instance.md index e017bd4b..ac2ddc5d 100644 --- a/docs/resources/opensearch_instance.md +++ b/docs/resources/opensearch_instance.md @@ -25,6 +25,7 @@ resource "stackit_opensearch_instance" "example" { ``` + ## Schema ### Required @@ -50,8 +51,22 @@ resource "stackit_opensearch_instance" "example" { - `plan_id` (String) The selected plan ID. + ### Nested Schema for `parameters` Optional: -- `sgw_acl` (String) +- `enable_monitoring` (Boolean) Enable monitoring. +- `graphite` (String) If set, monitoring with Graphite will be enabled. Expects the host and port where the Graphite metrics should be sent to (host:port). +- `java_garbage_collector` (String) The garbage collector to use for OpenSearch. +- `java_heapspace` (Number) The amount of memory (in MB) allocated as heap by the JVM for OpenSearch. +- `java_maxmetaspace` (Number) The amount of memory (in MB) used by the JVM to store metadata for OpenSearch. +- `max_disk_threshold` (Number) The maximum disk threshold in MB. If the disk usage exceeds this threshold, the instance will be stopped. +- `metrics_frequency` (Number) The frequency in seconds at which metrics are emitted (in seconds). +- `metrics_prefix` (String) The prefix for the metrics. Could be useful when using Graphite monitoring to prefix the metrics with a certain value, like an API key. +- `monitoring_instance_id` (String) The ID of the STACKIT monitoring instance. +- `plugins` (List of String) List of plugins to install. Must be a supported plugin name. The plugins `repository-s3` and `repository-azure` are enabled by default and cannot be disabled. +- `sgw_acl` (String) Comma separated list of IP networks in CIDR notation which are allowed to access this instance. +- `syslog` (List of String) List of syslog servers to send logs to. +- `tls_ciphers` (List of String) List of TLS ciphers to use. +- `tls_protocols` (String) The TLS protocol to use. diff --git a/stackit/internal/services/opensearch/instance/datasource.go b/stackit/internal/services/opensearch/instance/datasource.go index a6522743..246762c6 100644 --- a/stackit/internal/services/opensearch/instance/datasource.go +++ b/stackit/internal/services/opensearch/instance/datasource.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" @@ -86,6 +87,23 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques "plan_id": "The selected plan ID.", } + parametersDescriptions := map[string]string{ + "sgw_acl": "Comma separated list of IP networks in CIDR notation which are allowed to access this instance.", + "enable_monitoring": "Enable monitoring.", + "graphite": "If set, monitoring with Graphite will be enabled. Expects the host and port where the Graphite metrics should be sent to (host:port).", + "max_disk_threshold": "The maximum disk threshold in MB. If the disk usage exceeds this threshold, the instance will be stopped.", + "metrics_frequency": "The frequency in seconds at which metrics are emitted (in seconds).", + "metrics_prefix": "The prefix for the metrics. Could be useful when using Graphite monitoring to prefix the metrics with a certain value, like an API key.", + "monitoring_instance_id": "The ID of the STACKIT monitoring instance.", + "java_garbage_collector": "The garbage collector to use for OpenSearch.", + "java_heapspace": "The amount of memory (in MB) allocated as heap by the JVM for OpenSearch.", + "java_maxmetaspace": "The amount of memory (in MB) used by the JVM to store metadata for OpenSearch.", + "plugins": "List of plugins to install. Must be a supported plugin name. The plugins `repository-s3` and `repository-azure` are enabled by default and cannot be disabled.", + "syslog": "List of syslog servers to send logs to.", + "tls_ciphers": "List of TLS ciphers to use.", + "tls_protocols": "The TLS protocol to use.", + } + resp.Schema = schema.Schema{ Description: descriptions["main"], Attributes: map[string]schema.Attribute{ @@ -128,7 +146,63 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques "parameters": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "sgw_acl": schema.StringAttribute{ - Computed: true, + Description: parametersDescriptions["sgw_acl"], + Computed: true, + }, + "enable_monitoring": schema.BoolAttribute{ + Description: parametersDescriptions["enable_monitoring"], + Computed: true, + }, + "graphite": schema.StringAttribute{ + Description: parametersDescriptions["graphite"], + Computed: true, + }, + "java_garbage_collector": schema.StringAttribute{ + Description: parametersDescriptions["java_garbage_collector"], + Computed: true, + }, + "java_heapspace": schema.Int64Attribute{ + Description: parametersDescriptions["java_heapspace"], + Computed: true, + }, + "java_maxmetaspace": schema.Int64Attribute{ + Description: parametersDescriptions["java_maxmetaspace"], + Computed: true, + }, + "max_disk_threshold": schema.Int64Attribute{ + Description: parametersDescriptions["max_disk_threshold"], + Computed: true, + }, + "metrics_frequency": schema.Int64Attribute{ + Description: parametersDescriptions["metrics_frequency"], + Computed: true, + }, + "metrics_prefix": schema.StringAttribute{ + Description: parametersDescriptions["metrics_prefix"], + Computed: true, + }, + "monitoring_instance_id": schema.StringAttribute{ + Description: parametersDescriptions["monitoring_instance_id"], + Computed: true, + }, + "plugins": schema.ListAttribute{ + ElementType: types.StringType, + Description: parametersDescriptions["plugins"], + Computed: true, + }, + "syslog": schema.ListAttribute{ + ElementType: types.StringType, + Description: parametersDescriptions["syslog"], + Computed: true, + }, + "tls_ciphers": schema.ListAttribute{ + ElementType: types.StringType, + Description: parametersDescriptions["tls_ciphers"], + Computed: true, + }, + "tls_protocols": schema.StringAttribute{ + Description: parametersDescriptions["tls_protocols"], + Computed: true, }, }, Computed: true, diff --git a/stackit/internal/services/opensearch/instance/resource.go b/stackit/internal/services/opensearch/instance/resource.go index fc1da5e1..4c805eee 100644 --- a/stackit/internal/services/opensearch/instance/resource.go +++ b/stackit/internal/services/opensearch/instance/resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "slices" "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -52,12 +53,38 @@ type Model struct { // Struct corresponding to DataSourceModel.Parameters type parametersModel struct { - SgwAcl types.String `tfsdk:"sgw_acl"` + SgwAcl types.String `tfsdk:"sgw_acl"` + EnableMonitoring types.Bool `tfsdk:"enable_monitoring"` + Graphite types.String `tfsdk:"graphite"` + JavaGarbageCollector types.String `tfsdk:"java_garbage_collector"` + JavaHeapspace types.Int64 `tfsdk:"java_heapspace"` + JavaMaxmetaspace types.Int64 `tfsdk:"java_maxmetaspace"` + MaxDiskThreshold types.Int64 `tfsdk:"max_disk_threshold"` + MetricsFrequency types.Int64 `tfsdk:"metrics_frequency"` + MetricsPrefix types.String `tfsdk:"metrics_prefix"` + MonitoringInstanceId types.String `tfsdk:"monitoring_instance_id"` + Plugins types.List `tfsdk:"plugins"` + Syslog types.List `tfsdk:"syslog"` + TlsCiphers types.List `tfsdk:"tls_ciphers"` + TlsProtocols types.String `tfsdk:"tls_protocols"` } // Types corresponding to parametersModel var parametersTypes = map[string]attr.Type{ - "sgw_acl": basetypes.StringType{}, + "sgw_acl": basetypes.StringType{}, + "enable_monitoring": basetypes.BoolType{}, + "graphite": basetypes.StringType{}, + "java_garbage_collector": basetypes.StringType{}, + "java_heapspace": basetypes.Int64Type{}, + "java_maxmetaspace": basetypes.Int64Type{}, + "max_disk_threshold": basetypes.Int64Type{}, + "metrics_frequency": basetypes.Int64Type{}, + "metrics_prefix": basetypes.StringType{}, + "monitoring_instance_id": basetypes.StringType{}, + "plugins": basetypes.ListType{ElemType: types.StringType}, + "syslog": basetypes.ListType{ElemType: types.StringType}, + "tls_ciphers": basetypes.ListType{ElemType: types.StringType}, + "tls_protocols": basetypes.StringType{}, } // NewInstanceResource is a helper function to simplify the provider implementation. @@ -124,6 +151,23 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "plan_id": "The selected plan ID.", } + parametersDescriptions := map[string]string{ + "sgw_acl": "Comma separated list of IP networks in CIDR notation which are allowed to access this instance.", + "enable_monitoring": "Enable monitoring.", + "graphite": "If set, monitoring with Graphite will be enabled. Expects the host and port where the Graphite metrics should be sent to (host:port).", + "max_disk_threshold": "The maximum disk threshold in MB. If the disk usage exceeds this threshold, the instance will be stopped.", + "metrics_frequency": "The frequency in seconds at which metrics are emitted (in seconds).", + "metrics_prefix": "The prefix for the metrics. Could be useful when using Graphite monitoring to prefix the metrics with a certain value, like an API key.", + "monitoring_instance_id": "The ID of the STACKIT monitoring instance.", + "java_garbage_collector": "The garbage collector to use for OpenSearch.", + "java_heapspace": "The amount of memory (in MB) allocated as heap by the JVM for OpenSearch.", + "java_maxmetaspace": "The amount of memory (in MB) used by the JVM to store metadata for OpenSearch.", + "plugins": "List of plugins to install. Must be a supported plugin name. The plugins `repository-s3` and `repository-azure` are enabled by default and cannot be disabled.", + "syslog": "List of syslog servers to send logs to.", + "tls_ciphers": "List of TLS ciphers to use.", + "tls_protocols": "The TLS protocol to use.", + } + resp.Schema = schema.Schema{ Description: descriptions["main"], Attributes: map[string]schema.Attribute{ @@ -183,8 +227,77 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "parameters": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "sgw_acl": schema.StringAttribute{ - Optional: true, - Computed: true, + Description: parametersDescriptions["sgw_acl"], + Optional: true, + Computed: true, + }, + "enable_monitoring": schema.BoolAttribute{ + Description: parametersDescriptions["enable_monitoring"], + Optional: true, + Computed: true, + }, + "graphite": schema.StringAttribute{ + Description: parametersDescriptions["graphite"], + Optional: true, + Computed: true, + }, + "java_garbage_collector": schema.StringAttribute{ + Description: parametersDescriptions["java_garbage_collector"], + Optional: true, + Computed: true, + }, + "java_heapspace": schema.Int64Attribute{ + Description: parametersDescriptions["java_heapspace"], + Optional: true, + Computed: true, + }, + "java_maxmetaspace": schema.Int64Attribute{ + Description: parametersDescriptions["java_maxmetaspace"], + Optional: true, + Computed: true, + }, + "max_disk_threshold": schema.Int64Attribute{ + Description: parametersDescriptions["max_disk_threshold"], + Optional: true, + Computed: true, + }, + "metrics_frequency": schema.Int64Attribute{ + Description: parametersDescriptions["metrics_frequency"], + Optional: true, + Computed: true, + }, + "metrics_prefix": schema.StringAttribute{ + Description: parametersDescriptions["metrics_prefix"], + Optional: true, + Computed: true, + }, + "monitoring_instance_id": schema.StringAttribute{ + Description: parametersDescriptions["monitoring_instance_id"], + Optional: true, + Computed: true, + }, + "plugins": schema.ListAttribute{ + ElementType: types.StringType, + Description: parametersDescriptions["plugins"], + Optional: true, + Computed: true, + }, + "syslog": schema.ListAttribute{ + ElementType: types.StringType, + Description: parametersDescriptions["syslog"], + Optional: true, + Computed: true, + }, + "tls_ciphers": schema.ListAttribute{ + ElementType: types.StringType, + Description: parametersDescriptions["tls_ciphers"], + Optional: true, + Computed: true, + }, + "tls_protocols": schema.StringAttribute{ + Description: parametersDescriptions["tls_protocols"], + Optional: true, + Computed: true, }, }, Optional: true, @@ -235,8 +348,9 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques projectId := model.ProjectId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) - var parameters = ¶metersModel{} + var parameters *parametersModel if !(model.Parameters.IsNull() || model.Parameters.IsUnknown()) { + parameters = ¶metersModel{} diags = model.Parameters.As(ctx, parameters, basetypes.ObjectAsOptions{}) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -346,8 +460,9 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) - var parameters = ¶metersModel{} + var parameters *parametersModel if !(model.Parameters.IsNull() || model.Parameters.IsUnknown()) { + parameters := ¶metersModel{} diags = model.Parameters.As(ctx, parameters, basetypes.ObjectAsOptions{}) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -488,7 +603,22 @@ func mapFields(instance *opensearch.Instance, model *Model) error { func mapParameters(params map[string]interface{}) (types.Object, error) { attributes := map[string]attr.Value{} for attribute := range parametersTypes { - valueInterface, ok := params[attribute] + var valueInterface interface{} + var ok bool + + // This replacement is necessary because Terraform does not allow hyphens in attribute names + // And the API uses hyphens in some of the attribute names, which would cause a mismatch + // The following attributes have hyphens in the API but underscores in the schema + hyphenAttributes := []string{ + "tls_ciphers", + "tls_protocols", + } + if slices.Contains(hyphenAttributes, attribute) { + alteredAttribute := strings.ReplaceAll(attribute, "_", "-") + valueInterface, ok = params[alteredAttribute] + } else { + valueInterface, ok = params[attribute] + } if !ok { // All fields are optional, so this is ok // Set the value as nil, will be handled accordingly @@ -584,15 +714,9 @@ func toCreatePayload(model *Model, parameters *parametersModel) (*opensearch.Cre if model == nil { return nil, fmt.Errorf("nil model") } - if parameters == nil { - return &opensearch.CreateInstancePayload{ - InstanceName: conversion.StringValueToPointer(model.Name), - PlanId: conversion.StringValueToPointer(model.PlanId), - }, nil - } - payloadParams := &opensearch.InstanceParameters{} - if parameters.SgwAcl.ValueString() != "" { - payloadParams.SgwAcl = conversion.StringValueToPointer(parameters.SgwAcl) + payloadParams, err := toInstanceParams(parameters) + if err != nil { + return nil, fmt.Errorf("convert parameters: %w", err) } return &opensearch.CreateInstancePayload{ InstanceName: conversion.StringValueToPointer(model.Name), @@ -605,20 +729,53 @@ func toUpdatePayload(model *Model, parameters *parametersModel) (*opensearch.Par if model == nil { return nil, fmt.Errorf("nil model") } - - if parameters == nil { - return &opensearch.PartialUpdateInstancePayload{ - PlanId: conversion.StringValueToPointer(model.PlanId), - }, nil + payloadParams, err := toInstanceParams(parameters) + if err != nil { + return nil, fmt.Errorf("convert parameters: %w", err) } return &opensearch.PartialUpdateInstancePayload{ - Parameters: &opensearch.InstanceParameters{ - SgwAcl: conversion.StringValueToPointer(parameters.SgwAcl), - }, - PlanId: conversion.StringValueToPointer(model.PlanId), + Parameters: payloadParams, + PlanId: conversion.StringValueToPointer(model.PlanId), }, nil } +func toInstanceParams(parameters *parametersModel) (*opensearch.InstanceParameters, error) { + if parameters == nil { + return nil, nil + } + payloadParams := &opensearch.InstanceParameters{} + + payloadParams.SgwAcl = conversion.StringValueToPointer(parameters.SgwAcl) + payloadParams.EnableMonitoring = conversion.BoolValueToPointer(parameters.EnableMonitoring) + payloadParams.Graphite = conversion.StringValueToPointer(parameters.Graphite) + payloadParams.JavaGarbageCollector = conversion.StringValueToPointer(parameters.JavaGarbageCollector) + payloadParams.JavaHeapspace = conversion.Int64ValueToPointer(parameters.JavaHeapspace) + payloadParams.JavaMaxmetaspace = conversion.Int64ValueToPointer(parameters.JavaMaxmetaspace) + payloadParams.MaxDiskThreshold = conversion.Int64ValueToPointer(parameters.MaxDiskThreshold) + payloadParams.MetricsFrequency = conversion.Int64ValueToPointer(parameters.MetricsFrequency) + payloadParams.MetricsPrefix = conversion.StringValueToPointer(parameters.MetricsPrefix) + payloadParams.MonitoringInstanceId = conversion.StringValueToPointer(parameters.MonitoringInstanceId) + payloadParams.TlsProtocols = conversion.StringValueToPointer(parameters.TlsProtocols) + + var err error + payloadParams.Plugins, err = conversion.StringListToPointer(parameters.Plugins) + if err != nil { + return nil, fmt.Errorf("convert plugins: %w", err) + } + + payloadParams.Syslog, err = conversion.StringListToPointer(parameters.Syslog) + if err != nil { + return nil, fmt.Errorf("convert syslog: %w", err) + } + + payloadParams.TlsCiphers, err = conversion.StringListToPointer(parameters.TlsCiphers) + if err != nil { + return nil, fmt.Errorf("convert tls_ciphers: %w", err) + } + + return payloadParams, nil +} + func (r *instanceResource) loadPlanId(ctx context.Context, model *Model) error { projectId := model.ProjectId.ValueString() res, err := r.client.ListOfferings(ctx, projectId).Execute() diff --git a/stackit/internal/services/opensearch/instance/resource_test.go b/stackit/internal/services/opensearch/instance/resource_test.go index f2403969..b86f5470 100644 --- a/stackit/internal/services/opensearch/instance/resource_test.go +++ b/stackit/internal/services/opensearch/instance/resource_test.go @@ -1,15 +1,77 @@ package opensearch import ( + "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/opensearch" ) +var fixtureModelParameters = types.ObjectValueMust(parametersTypes, map[string]attr.Value{ + "sgw_acl": types.StringValue("acl"), + "enable_monitoring": types.BoolValue(true), + "graphite": types.StringValue("graphite"), + "java_garbage_collector": types.StringValue("gc"), + "java_heapspace": types.Int64Value(10), + "java_maxmetaspace": types.Int64Value(10), + "max_disk_threshold": types.Int64Value(10), + "metrics_frequency": types.Int64Value(10), + "metrics_prefix": types.StringValue("prefix"), + "monitoring_instance_id": types.StringValue("mid"), + "plugins": types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("plugin"), + types.StringValue("plugin2"), + }), + "syslog": types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("syslog"), + types.StringValue("syslog2"), + }), + "tls_ciphers": types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("cipher"), + types.StringValue("cipher2"), + }), + "tls_protocols": types.StringValue("protocol"), +}) + +var fixtureNullModelParameters = types.ObjectValueMust(parametersTypes, map[string]attr.Value{ + "sgw_acl": types.StringNull(), + "enable_monitoring": types.BoolNull(), + "graphite": types.StringNull(), + "java_garbage_collector": types.StringNull(), + "java_heapspace": types.Int64Null(), + "java_maxmetaspace": types.Int64Null(), + "max_disk_threshold": types.Int64Null(), + "metrics_frequency": types.Int64Null(), + "metrics_prefix": types.StringNull(), + "monitoring_instance_id": types.StringNull(), + "plugins": types.ListNull(types.StringType), + "syslog": types.ListNull(types.StringType), + "tls_ciphers": types.ListNull(types.StringType), + "tls_protocols": types.StringNull(), +}) + +var fixtureInstanceParameters = opensearch.InstanceParameters{ + SgwAcl: utils.Ptr("acl"), + EnableMonitoring: utils.Ptr(true), + Graphite: utils.Ptr("graphite"), + JavaGarbageCollector: utils.Ptr("gc"), + JavaHeapspace: utils.Ptr(int64(10)), + JavaMaxmetaspace: utils.Ptr(int64(10)), + MaxDiskThreshold: utils.Ptr(int64(10)), + MetricsFrequency: utils.Ptr(int64(10)), + MetricsPrefix: utils.Ptr("prefix"), + MonitoringInstanceId: utils.Ptr("mid"), + Plugins: &[]string{"plugin", "plugin2"}, + Syslog: &[]string{"syslog", "syslog2"}, + TlsCiphers: &[]string{"cipher", "cipher2"}, + TlsProtocols: utils.Ptr("protocol"), +} + func TestMapFields(t *testing.T) { tests := []struct { description string @@ -47,7 +109,21 @@ func TestMapFields(t *testing.T) { Name: utils.Ptr("name"), CfOrganizationGuid: utils.Ptr("org"), Parameters: &map[string]interface{}{ - "sgw_acl": "acl", + // Using "-" on purpose on some fields because that is the API response + "sgw_acl": "acl", + "enable_monitoring": true, + "graphite": "graphite", + "java_garbage_collector": "gc", + "java_heapspace": int64(10), + "java_maxmetaspace": int64(10), + "max_disk_threshold": int64(10), + "metrics_frequency": int64(10), + "metrics_prefix": "prefix", + "monitoring_instance_id": "mid", + "plugins": []string{"plugin", "plugin2"}, + "syslog": []string{"syslog", "syslog2"}, + "tls-ciphers": []string{"cipher", "cipher2"}, + "tls-protocols": "protocol", }, }, Model{ @@ -61,9 +137,7 @@ func TestMapFields(t *testing.T) { DashboardUrl: types.StringValue("dashboard"), ImageUrl: types.StringValue("image"), CfOrganizationGuid: types.StringValue("org"), - Parameters: types.ObjectValueMust(parametersTypes, map[string]attr.Value{ - "sgw_acl": types.StringValue("acl"), - }), + Parameters: fixtureModelParameters, }, true, }, @@ -125,61 +199,48 @@ func TestMapFields(t *testing.T) { func TestToCreatePayload(t *testing.T) { tests := []struct { - description string - input *Model - inputParameters *parametersModel - expected *opensearch.CreateInstancePayload - isValid bool + description string + input *Model + expected *opensearch.CreateInstancePayload + isValid bool }{ { "default_values", &Model{}, - ¶metersModel{}, - &opensearch.CreateInstancePayload{ - Parameters: &opensearch.InstanceParameters{}, - }, + &opensearch.CreateInstancePayload{}, true, }, { "simple_values", &Model{ - Name: types.StringValue("name"), - PlanId: types.StringValue("plan"), - }, - ¶metersModel{ - SgwAcl: types.StringValue("sgw"), + Name: types.StringValue("name"), + PlanId: types.StringValue("plan"), + Parameters: fixtureModelParameters, }, &opensearch.CreateInstancePayload{ InstanceName: utils.Ptr("name"), - Parameters: &opensearch.InstanceParameters{ - SgwAcl: utils.Ptr("sgw"), - }, - PlanId: utils.Ptr("plan"), + Parameters: &fixtureInstanceParameters, + PlanId: utils.Ptr("plan"), }, true, }, { "null_fields_and_int_conversions", &Model{ - Name: types.StringValue(""), - PlanId: types.StringValue(""), - }, - ¶metersModel{ - SgwAcl: types.StringNull(), + Name: types.StringValue(""), + PlanId: types.StringValue(""), + Parameters: fixtureNullModelParameters, }, &opensearch.CreateInstancePayload{ InstanceName: utils.Ptr(""), - Parameters: &opensearch.InstanceParameters{ - SgwAcl: nil, - }, - PlanId: utils.Ptr(""), + Parameters: &opensearch.InstanceParameters{}, + PlanId: utils.Ptr(""), }, true, }, { "nil_model", nil, - ¶metersModel{}, nil, false, }, @@ -189,7 +250,6 @@ func TestToCreatePayload(t *testing.T) { Name: types.StringValue("name"), PlanId: types.StringValue("plan"), }, - nil, &opensearch.CreateInstancePayload{ InstanceName: utils.Ptr("name"), PlanId: utils.Ptr("plan"), @@ -199,7 +259,17 @@ func TestToCreatePayload(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - output, err := toCreatePayload(tt.input, tt.inputParameters) + var parameters *parametersModel + if tt.input != nil { + if !(tt.input.Parameters.IsNull() || tt.input.Parameters.IsUnknown()) { + parameters = ¶metersModel{} + diags := tt.input.Parameters.As(context.Background(), parameters, basetypes.ObjectAsOptions{}) + if diags.HasError() { + t.Fatalf("Error converting parameters: %v", diags.Errors()) + } + } + } + output, err := toCreatePayload(tt.input, parameters) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } @@ -218,57 +288,44 @@ func TestToCreatePayload(t *testing.T) { func TestToUpdatePayload(t *testing.T) { tests := []struct { - description string - input *Model - inputParameters *parametersModel - expected *opensearch.PartialUpdateInstancePayload - isValid bool + description string + input *Model + expected *opensearch.PartialUpdateInstancePayload + isValid bool }{ { "default_values", &Model{}, - ¶metersModel{}, - &opensearch.PartialUpdateInstancePayload{ - Parameters: &opensearch.InstanceParameters{}, - }, + &opensearch.PartialUpdateInstancePayload{}, true, }, { "simple_values", &Model{ - PlanId: types.StringValue("plan"), - }, - ¶metersModel{ - SgwAcl: types.StringValue("sgw"), + PlanId: types.StringValue("plan"), + Parameters: fixtureModelParameters, }, &opensearch.PartialUpdateInstancePayload{ - Parameters: &opensearch.InstanceParameters{ - SgwAcl: utils.Ptr("sgw"), - }, - PlanId: utils.Ptr("plan"), + Parameters: &fixtureInstanceParameters, + PlanId: utils.Ptr("plan"), }, true, }, { "null_fields_and_int_conversions", &Model{ - PlanId: types.StringValue(""), - }, - ¶metersModel{ - SgwAcl: types.StringNull(), + PlanId: types.StringValue(""), + Parameters: fixtureNullModelParameters, }, &opensearch.PartialUpdateInstancePayload{ - Parameters: &opensearch.InstanceParameters{ - SgwAcl: nil, - }, - PlanId: utils.Ptr(""), + Parameters: &opensearch.InstanceParameters{}, + PlanId: utils.Ptr(""), }, true, }, { "nil_model", nil, - ¶metersModel{}, nil, false, }, @@ -277,7 +334,6 @@ func TestToUpdatePayload(t *testing.T) { &Model{ PlanId: types.StringValue("plan"), }, - nil, &opensearch.PartialUpdateInstancePayload{ PlanId: utils.Ptr("plan"), }, @@ -286,7 +342,17 @@ func TestToUpdatePayload(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - output, err := toUpdatePayload(tt.input, tt.inputParameters) + var parameters *parametersModel + if tt.input != nil { + if !(tt.input.Parameters.IsNull() || tt.input.Parameters.IsUnknown()) { + parameters = ¶metersModel{} + diags := tt.input.Parameters.As(context.Background(), parameters, basetypes.ObjectAsOptions{}) + if diags.HasError() { + t.Fatalf("Error converting parameters: %v", diags.Errors()) + } + } + } + output, err := toUpdatePayload(tt.input, parameters) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/opensearch/opensearch_acc_test.go b/stackit/internal/services/opensearch/opensearch_acc_test.go index 0f0b08ca..64117f9a 100644 --- a/stackit/internal/services/opensearch/opensearch_acc_test.go +++ b/stackit/internal/services/opensearch/opensearch_acc_test.go @@ -18,15 +18,42 @@ import ( // Instance resource data var instanceResource = map[string]string{ - "project_id": testutil.ProjectId, - "name": testutil.ResourceNameWithDateTime("opensearch"), - "plan_id": "9e4eac4b-b03d-4d7b-b01b-6d1224aa2d68", - "plan_name": "stackit-opensearch-1.2.10-replica", - "version": "2", - "sgw_acl": "192.168.0.0/24", + "project_id": testutil.ProjectId, + "name": testutil.ResourceNameWithDateTime("opensearch"), + "plan_id": "9e4eac4b-b03d-4d7b-b01b-6d1224aa2d68", + "plan_name": "stackit-opensearch-1.2.10-replica", + "version": "2", + "sgw_acl-1": "192.168.0.0/16", + "sgw_acl-2": "192.168.0.0/24", + "max_disk_threshold": "80", + "enable_monitoring": "false", + "syslog-0": "syslog.example.com:514", } -func resourceConfig() string { +func parametersConfig(params map[string]string) string { + nonStringParams := []string{ + "enable_monitoring", + "max_disk_threshold", + "metrics_frequency", + "java_heapspace", + "java_maxmetaspace", + "plugins", + "syslog", + "tls_ciphers", + } + parameters := "parameters = {" + for k, v := range params { + if utils.Contains(nonStringParams, k) { + parameters += fmt.Sprintf("%s = %s\n", k, v) + } else { + parameters += fmt.Sprintf("%s = %q\n", k, v) + } + } + parameters += "\n}" + return parameters +} + +func resourceConfig(params map[string]string) string { return fmt.Sprintf(` %s @@ -35,6 +62,7 @@ func resourceConfig() string { name = "%s" plan_name = "%s" version = "%s" + %s } resource "stackit_opensearch_credential" "credential" { @@ -47,34 +75,7 @@ func resourceConfig() string { instanceResource["name"], instanceResource["plan_name"], instanceResource["version"], - ) -} - -func resourceConfigUpdate() string { - return fmt.Sprintf(` - %s - - resource "stackit_opensearch_instance" "instance" { - project_id = "%s" - name = "%s" - plan_name = "%s" - version = "%s" - parameters = { - sgw_acl = "%s" - } - } - - resource "stackit_opensearch_credential" "credential" { - project_id = stackit_opensearch_instance.instance.project_id - instance_id = stackit_opensearch_instance.instance.instance_id - } - `, - testutil.OpenSearchProviderConfig(), - instanceResource["project_id"], - instanceResource["name"], - instanceResource["plan_name"], - instanceResource["version"], - instanceResource["sgw_acl"], + parametersConfig(params), ) } @@ -86,7 +87,13 @@ func TestAccOpenSearchResource(t *testing.T) { // Creation { - Config: resourceConfig(), + Config: resourceConfig( + map[string]string{ + "sgw_acl": instanceResource["sgw_acl-1"], + "max_disk_threshold": instanceResource["max_disk_threshold"], + "enable_monitoring": instanceResource["enable_monitoring"], + "syslog": fmt.Sprintf(`[%q]`, instanceResource["syslog-0"]), + }), Check: resource.ComposeAggregateTestCheckFunc( // Instance data resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "project_id", instanceResource["project_id"]), @@ -95,7 +102,11 @@ func TestAccOpenSearchResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "plan_name", instanceResource["plan_name"]), resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "version", instanceResource["version"]), resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "name", instanceResource["name"]), - resource.TestCheckResourceAttrSet("stackit_opensearch_instance.instance", "parameters.sgw_acl"), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.sgw_acl", instanceResource["sqw_acl-1"]), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.max_disk_threshold", instanceResource["max_disk_threshold"]), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.enable_monitoring", instanceResource["enable_monitoring"]), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.syslog.#", "1"), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.syslog.0", instanceResource["syslog-0"]), // Credential data resource.TestCheckResourceAttrPair( @@ -125,7 +136,13 @@ func TestAccOpenSearchResource(t *testing.T) { instance_id = stackit_opensearch_credential.credential.instance_id credential_id = stackit_opensearch_credential.credential.credential_id }`, - resourceConfig(), + resourceConfig( + map[string]string{ + "sgw_acl": instanceResource["sgw_acl-1"], + "max_disk_threshold": instanceResource["max_disk_threshold"], + "enable_monitoring": instanceResource["enable_monitoring"], + "syslog": fmt.Sprintf(`[%q]`, instanceResource["syslog-0"]), + }), ), Check: resource.ComposeAggregateTestCheckFunc( // Instance data @@ -136,7 +153,11 @@ func TestAccOpenSearchResource(t *testing.T) { "data.stackit_opensearch_credential.credential", "credential_id"), resource.TestCheckResourceAttr("data.stackit_opensearch_instance.instance", "plan_id", instanceResource["plan_id"]), resource.TestCheckResourceAttr("data.stackit_opensearch_instance.instance", "name", instanceResource["name"]), - resource.TestCheckResourceAttrSet("data.stackit_opensearch_instance.instance", "parameters.sgw_acl"), + resource.TestCheckResourceAttr("data.stackit_opensearch_instance.instance", "parameters.sgw_acl", instanceResource["sqw_acl-1"]), + resource.TestCheckResourceAttr("data.stackit_opensearch_instance.instance", "parameters.max_disk_threshold", instanceResource["max_disk_threshold"]), + resource.TestCheckResourceAttr("data.stackit_opensearch_instance.instance", "parameters.enable_monitoring", instanceResource["enable_monitoring"]), + resource.TestCheckResourceAttr("data.stackit_opensearch_instance.instance", "parameters.syslog.#", "1"), + resource.TestCheckResourceAttr("data.stackit_opensearch_instance.instance", "parameters.syslog.0", instanceResource["syslog-0"]), // Credential data resource.TestCheckResourceAttr("data.stackit_opensearch_credential.credential", "project_id", instanceResource["project_id"]), @@ -187,7 +208,13 @@ func TestAccOpenSearchResource(t *testing.T) { }, // Update { - Config: resourceConfigUpdate(), + Config: resourceConfig( + map[string]string{ + "sgw_acl": instanceResource["sgw_acl-2"], + "max_disk_threshold": instanceResource["max_disk_threshold"], + "enable_monitoring": instanceResource["enable_monitoring"], + "syslog": fmt.Sprintf(`[%q]`, instanceResource["syslog-0"]), + }), Check: resource.ComposeAggregateTestCheckFunc( // Instance data resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "project_id", instanceResource["project_id"]), @@ -196,7 +223,11 @@ func TestAccOpenSearchResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "plan_name", instanceResource["plan_name"]), resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "version", instanceResource["version"]), resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "name", instanceResource["name"]), - resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.sgw_acl", instanceResource["sgw_acl"]), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.sgw_acl", instanceResource["sqw_acl-2"]), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.max_disk_threshold", instanceResource["max_disk_threshold"]), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.enable_monitoring", instanceResource["enable_monitoring"]), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.syslog.#", "1"), + resource.TestCheckResourceAttr("stackit_opensearch_instance.instance", "parameters.syslog.0", instanceResource["syslog-0"]), ), }, // Deletion is done by the framework implicitly