From 26c124b038082c7c48d81a1ea3f1f42c42351d46 Mon Sep 17 00:00:00 2001 From: Boris Ilyushonak <57406418+biscout42@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:52:50 +0200 Subject: [PATCH] fix for http/tcp monitor produces inconsistent result after apply (#801) * fix: synthetics http monitor creations produced inconsistent result after apply for https://github.com/elastic/terraform-provider-elasticstack/issues/800 fix for the fileds: http.locations, http.proxy_url, http.ssl_supported_protocols, http.ssl_verification_mode, tcp.proxy_url, tcp.ssl_supported_protocols, tcp.ssl_verification_mode * make lint happy * update change log * cr comments - reuse utls --- CHANGELOG.md | 1 + internal/kibana/synthetics/acc_test.go | 22 ++-- internal/kibana/synthetics/create.go | 8 +- internal/kibana/synthetics/read.go | 6 +- internal/kibana/synthetics/schema.go | 118 +++++++++++------ internal/kibana/synthetics/schema_test.go | 150 ++++++++++++---------- internal/kibana/synthetics/update.go | 8 +- 7 files changed, 184 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d87c578d..0c5da485b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Fix handling of `sys_monitoring` in `elasticstack_fleet_agent_policy` ([#792](https://github.com/elastic/terraform-provider-elasticstack/pull/792)) - Migrate `elasticstack_fleet_agent_policy`, `elasticstack_fleet_integration` (both), and `elasticstack_fleet_server_host` to terraform-plugin-framework ([#785](https://github.com/elastic/terraform-provider-elasticstack/pull/785)) +- Fix for synthetics http/tcp monitor produces inconsistent result after apply ([#801](https://github.com/elastic/terraform-provider-elasticstack/pull/801)) ## [0.11.7] - 2024-09-20 diff --git a/internal/kibana/synthetics/acc_test.go b/internal/kibana/synthetics/acc_test.go index 3034ff000..664a5a847 100644 --- a/internal/kibana/synthetics/acc_test.go +++ b/internal/kibana/synthetics/acc_test.go @@ -37,12 +37,9 @@ resource "elasticstack_kibana_synthetics_monitor" "%s" { timeout = 30 http = { url = "http://localhost:5601" - ssl_verification_mode = "full" - ssl_supported_protocols = ["TLSv1.0", "TLSv1.1", "TLSv1.2"] mode = "any" ipv4 = true ipv6 = false - proxy_url = "http://localhost:8080" } } ` @@ -128,9 +125,6 @@ resource "elasticstack_kibana_synthetics_monitor" "%s" { timeout = 30 tcp = { host = "http://localhost:5601" - ssl_verification_mode = "full" - ssl_supported_protocols = ["TLSv1.0", "TLSv1.1", "TLSv1.2"] - proxy_url = "http://localhost:8080" proxy_use_local_resolver = true } } @@ -302,14 +296,14 @@ func TestSyntheticMonitorHTTPResource(t *testing.T) { resource.TestCheckResourceAttr(httpMonitorId, "http.url", "http://localhost:5601"), resource.TestCheckResourceAttr(httpMonitorId, "http.ssl_verification_mode", "full"), resource.TestCheckResourceAttr(httpMonitorId, "http.ssl_supported_protocols.#", "3"), - resource.TestCheckResourceAttr(httpMonitorId, "http.ssl_supported_protocols.0", "TLSv1.0"), - resource.TestCheckResourceAttr(httpMonitorId, "http.ssl_supported_protocols.1", "TLSv1.1"), - resource.TestCheckResourceAttr(httpMonitorId, "http.ssl_supported_protocols.2", "TLSv1.2"), + resource.TestCheckResourceAttr(httpMonitorId, "http.ssl_supported_protocols.0", "TLSv1.1"), + resource.TestCheckResourceAttr(httpMonitorId, "http.ssl_supported_protocols.1", "TLSv1.2"), + resource.TestCheckResourceAttr(httpMonitorId, "http.ssl_supported_protocols.2", "TLSv1.3"), resource.TestCheckResourceAttr(httpMonitorId, "http.max_redirects", "0"), resource.TestCheckResourceAttr(httpMonitorId, "http.mode", "any"), resource.TestCheckResourceAttr(httpMonitorId, "http.ipv4", "true"), resource.TestCheckResourceAttr(httpMonitorId, "http.ipv6", "false"), - resource.TestCheckResourceAttr(httpMonitorId, "http.proxy_url", "http://localhost:8080"), + resource.TestCheckResourceAttr(httpMonitorId, "http.proxy_url", ""), ), }, // ImportState testing @@ -402,10 +396,10 @@ func TestSyntheticMonitorTCPResource(t *testing.T) { resource.TestCheckResourceAttr(tcpMonitorId, "tcp.host", "http://localhost:5601"), resource.TestCheckResourceAttr(tcpMonitorId, "tcp.ssl_verification_mode", "full"), resource.TestCheckResourceAttr(tcpMonitorId, "tcp.ssl_supported_protocols.#", "3"), - resource.TestCheckResourceAttr(tcpMonitorId, "tcp.ssl_supported_protocols.0", "TLSv1.0"), - resource.TestCheckResourceAttr(tcpMonitorId, "tcp.ssl_supported_protocols.1", "TLSv1.1"), - resource.TestCheckResourceAttr(tcpMonitorId, "tcp.ssl_supported_protocols.2", "TLSv1.2"), - resource.TestCheckResourceAttr(tcpMonitorId, "tcp.proxy_url", "http://localhost:8080"), + resource.TestCheckResourceAttr(tcpMonitorId, "tcp.ssl_supported_protocols.0", "TLSv1.1"), + resource.TestCheckResourceAttr(tcpMonitorId, "tcp.ssl_supported_protocols.1", "TLSv1.2"), + resource.TestCheckResourceAttr(tcpMonitorId, "tcp.ssl_supported_protocols.2", "TLSv1.3"), + resource.TestCheckResourceAttr(tcpMonitorId, "tcp.proxy_url", ""), resource.TestCheckResourceAttr(tcpMonitorId, "tcp.proxy_use_local_resolver", "true"), ), }, diff --git a/internal/kibana/synthetics/create.go b/internal/kibana/synthetics/create.go index 26cc9285f..830b9aa73 100644 --- a/internal/kibana/synthetics/create.go +++ b/internal/kibana/synthetics/create.go @@ -20,7 +20,7 @@ func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, r return } - input, diags := plan.toKibanaAPIRequest() + input, diags := plan.toKibanaAPIRequest(ctx) response.Diagnostics.Append(diags...) if response.Diagnostics.HasError() { return @@ -33,9 +33,9 @@ func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, r return } - plan, err = plan.toModelV0(result) - if err != nil { - response.Diagnostics.AddError("Failed to convert Kibana monitor API to TF state", err.Error()) + plan, diags = plan.toModelV0(ctx, result) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { return } diff --git a/internal/kibana/synthetics/read.go b/internal/kibana/synthetics/read.go index d38799baa..e7a13db7f 100644 --- a/internal/kibana/synthetics/read.go +++ b/internal/kibana/synthetics/read.go @@ -42,9 +42,9 @@ func (r *Resource) Read(ctx context.Context, request resource.ReadRequest, respo return } - state, err = state.toModelV0(result) - if err != nil { - response.Diagnostics.AddError("Failed to convert Kibana monitor API to TF state", err.Error()) + state, diags = state.toModelV0(ctx, result) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { return } diff --git a/internal/kibana/synthetics/schema.go b/internal/kibana/synthetics/schema.go index 2d69c285f..60db6cd1a 100644 --- a/internal/kibana/synthetics/schema.go +++ b/internal/kibana/synthetics/schema.go @@ -1,18 +1,22 @@ package synthetics import ( + "context" "encoding/json" "fmt" "github.com/disaster37/go-kibana-rest/v8/kbapi" "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "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/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "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" @@ -41,7 +45,7 @@ type tfAlertConfigV0 struct { type tfHTTPMonitorFieldsV0 struct { URL types.String `tfsdk:"url"` SslVerificationMode types.String `tfsdk:"ssl_verification_mode"` - SslSupportedProtocols []types.String `tfsdk:"ssl_supported_protocols"` + SslSupportedProtocols types.List `tfsdk:"ssl_supported_protocols"` MaxRedirects types.Int64 `tfsdk:"max_redirects"` Mode types.String `tfsdk:"mode"` IPv4 types.Bool `tfsdk:"ipv4"` @@ -55,13 +59,13 @@ type tfHTTPMonitorFieldsV0 struct { } type tfTCPMonitorFieldsV0 struct { - Host types.String `tfsdk:"host"` - SslVerificationMode types.String `tfsdk:"ssl_verification_mode"` - SslSupportedProtocols []types.String `tfsdk:"ssl_supported_protocols"` - CheckSend types.String `tfsdk:"check_send"` - CheckReceive types.String `tfsdk:"check_receive"` - ProxyURL types.String `tfsdk:"proxy_url"` - ProxyUseLocalResolver types.Bool `tfsdk:"proxy_use_local_resolver"` + Host types.String `tfsdk:"host"` + SslVerificationMode types.String `tfsdk:"ssl_verification_mode"` + SslSupportedProtocols types.List `tfsdk:"ssl_supported_protocols"` + CheckSend types.String `tfsdk:"check_send"` + CheckReceive types.String `tfsdk:"check_receive"` + ProxyURL types.String `tfsdk:"proxy_url"` + ProxyUseLocalResolver types.Bool `tfsdk:"proxy_use_local_resolver"` } type tfICMPMonitorFieldsV0 struct { @@ -293,11 +297,15 @@ func httpMonitorFieldsSchema() schema.Attribute { "ssl_verification_mode": schema.StringAttribute{ Optional: true, MarkdownDescription: "Controls the verification of server certificates. ", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "ssl_supported_protocols": schema.ListAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "List of allowed SSL/TLS versions.", + Computed: true, + PlanModifiers: []planmodifier.List{listplanmodifier.UseStateForUnknown()}, }, "max_redirects": schema.Int64Attribute{ Optional: true, @@ -332,6 +340,8 @@ func httpMonitorFieldsSchema() schema.Attribute { "proxy_url": schema.StringAttribute{ Optional: true, MarkdownDescription: "The URL of the proxy to use for this monitor.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "response": jsonObjectSchema("Controls the indexing of the HTTP response body contents to the `http.response.body.contents` field."), "check": jsonObjectSchema("The check request settings."), @@ -352,11 +362,15 @@ func tcpMonitorFieldsSchema() schema.Attribute { "ssl_verification_mode": schema.StringAttribute{ Optional: true, MarkdownDescription: "Controls the verification of server certificates. ", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "ssl_supported_protocols": schema.ListAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "List of allowed SSL/TLS versions.", + Computed: true, + PlanModifiers: []planmodifier.List{listplanmodifier.UseStateForUnknown()}, }, "check_send": schema.StringAttribute{ Optional: true, @@ -369,6 +383,8 @@ func tcpMonitorFieldsSchema() schema.Attribute { "proxy_url": schema.StringAttribute{ Optional: true, MarkdownDescription: "The URL of the SOCKS5 proxy to use when connecting to the server. The value must be a URL with a scheme of `socks5://`. If the SOCKS5 proxy server requires client authentication, then a username and password can be embedded in the URL. When using a proxy, hostnames are resolved on the proxy server instead of on the client. You can change this behavior by setting the `proxy_use_local_resolver` option.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "proxy_use_local_resolver": schema.BoolAttribute{ Optional: true, @@ -464,20 +480,22 @@ func stringToInt64(v string) (int64, error) { return res, err } -func (v *tfModelV0) toModelV0(api *kbapi.SyntheticsMonitor) (*tfModelV0, error) { +func (v *tfModelV0) toModelV0(ctx context.Context, api *kbapi.SyntheticsMonitor) (*tfModelV0, diag.Diagnostics) { var schedule int64 var err error + dg := diag.Diagnostics{} if api.Schedule != nil { schedule, err = stringToInt64(api.Schedule.Number) if err != nil { - return nil, err + dg.AddError("Failed to convert schedule to int64", err.Error()) + return nil, dg } } var locLabels []string var privateLocLabels []string for _, l := range api.Locations { if l.IsServiceManaged { - locLabels = append(locLabels, l.Label) + locLabels = append(locLabels, l.Id) } else { privateLocLabels = append(privateLocLabels, l.Label) } @@ -485,7 +503,8 @@ func (v *tfModelV0) toModelV0(api *kbapi.SyntheticsMonitor) (*tfModelV0, error) timeout, err := stringToInt64(string(api.Timeout)) if err != nil { - return nil, err + dg.AddError("Failed to convert timeout to int64", err.Error()) + return nil, dg } var http *tfHTTPMonitorFieldsV0 @@ -499,13 +518,13 @@ func (v *tfModelV0) toModelV0(api *kbapi.SyntheticsMonitor) (*tfModelV0, error) if v.HTTP != nil { http = v.HTTP } - http, err = http.toTfHTTPMonitorFieldsV0(api) + http = http.toTfHTTPMonitorFieldsV0(ctx, dg, api) case kbapi.Tcp: tcp = &tfTCPMonitorFieldsV0{} if v.TCP != nil { tcp = v.TCP } - tcp, err = tcp.toTfTCPMonitorFieldsV0(api) + tcp = tcp.toTfTCPMonitorFieldsV0(ctx, dg, api) case kbapi.Icmp: icmp = &tfICMPMonitorFieldsV0{} if v.ICMP != nil { @@ -523,14 +542,16 @@ func (v *tfModelV0) toModelV0(api *kbapi.SyntheticsMonitor) (*tfModelV0, error) } if err != nil { - return nil, err + dg.AddError("Failed to convert monitor fields", err.Error()) + return nil, dg } params := v.Params if api.Params != nil { params, err = toNormalizedValue(api.Params) if err != nil { - return nil, err + dg.AddError("Failed to parse params", err.Error()) + return nil, dg } } @@ -557,10 +578,10 @@ func (v *tfModelV0) toModelV0(api *kbapi.SyntheticsMonitor) (*tfModelV0, error) ICMP: icmp, Browser: browser, RetestOnFailure: v.RetestOnFailure, - }, nil + }, dg } -func (v *tfTCPMonitorFieldsV0) toTfTCPMonitorFieldsV0(api *kbapi.SyntheticsMonitor) (*tfTCPMonitorFieldsV0, error) { +func (v *tfTCPMonitorFieldsV0) toTfTCPMonitorFieldsV0(ctx context.Context, dg diag.Diagnostics, api *kbapi.SyntheticsMonitor) *tfTCPMonitorFieldsV0 { checkSend := v.CheckSend if api.CheckSend != "" { checkSend = types.StringValue(api.CheckSend) @@ -569,15 +590,19 @@ func (v *tfTCPMonitorFieldsV0) toTfTCPMonitorFieldsV0(api *kbapi.SyntheticsMonit if api.CheckReceive != "" { checkReceive = types.StringValue(api.CheckReceive) } + sslSupportedProtocols := utils.SliceToListType_String(ctx, api.SslSupportedProtocols, path.Root("tcp").AtName("ssl_supported_protocols"), dg) + if dg.HasError() { + return nil + } return &tfTCPMonitorFieldsV0{ Host: types.StringValue(api.Host), SslVerificationMode: types.StringValue(api.SslVerificationMode), - SslSupportedProtocols: StringSliceValue(api.SslSupportedProtocols), + SslSupportedProtocols: sslSupportedProtocols, CheckSend: checkSend, CheckReceive: checkReceive, ProxyURL: types.StringValue(api.ProxyUrl), ProxyUseLocalResolver: types.BoolPointerValue(api.ProxyUseLocalResolver), - }, nil + } } func (v *tfICMPMonitorFieldsV0) toTfICMPMonitorFieldsV0(api *kbapi.SyntheticsMonitor) (*tfICMPMonitorFieldsV0, error) { @@ -621,14 +646,15 @@ func (v *tfBrowserMonitorFieldsV0) toTfBrowserMonitorFieldsV0(api *kbapi.Synthet }, nil } -func (v *tfHTTPMonitorFieldsV0) toTfHTTPMonitorFieldsV0(api *kbapi.SyntheticsMonitor) (*tfHTTPMonitorFieldsV0, error) { +func (v *tfHTTPMonitorFieldsV0) toTfHTTPMonitorFieldsV0(ctx context.Context, dg diag.Diagnostics, api *kbapi.SyntheticsMonitor) *tfHTTPMonitorFieldsV0 { var err error proxyHeaders := v.ProxyHeader if api.ProxyHeaders != nil { proxyHeaders, err = toNormalizedValue(api.ProxyHeaders) if err != nil { - return nil, err + dg.AddError("Failed to parse proxy_headers", err.Error()) + return nil } } @@ -643,13 +669,19 @@ func (v *tfHTTPMonitorFieldsV0) toTfHTTPMonitorFieldsV0(api *kbapi.SyntheticsMon maxRedirects, err := stringToInt64(api.MaxRedirects) if err != nil { - return nil, err + dg.AddError("Failed to parse max_redirects", err.Error()) + return nil } + sslSupportedProtocols := utils.SliceToListType_String(ctx, api.SslSupportedProtocols, path.Root("http").AtName("ssl_supported_protocols"), dg) + + if dg.HasError() { + return nil + } return &tfHTTPMonitorFieldsV0{ URL: types.StringValue(api.Url), SslVerificationMode: types.StringValue(api.SslVerificationMode), - SslSupportedProtocols: StringSliceValue(api.SslSupportedProtocols), + SslSupportedProtocols: sslSupportedProtocols, MaxRedirects: types.Int64Value(maxRedirects), Mode: types.StringValue(string(api.Mode)), IPv4: types.BoolPointerValue(api.Ipv4), @@ -660,7 +692,7 @@ func (v *tfHTTPMonitorFieldsV0) toTfHTTPMonitorFieldsV0(api *kbapi.SyntheticsMon ProxyURL: types.StringValue(api.ProxyUrl), Check: v.Check, Response: v.Response, - }, nil + } } func toTfAlertConfigV0(alert *kbapi.MonitorAlertConfig) *tfAlertConfigV0 { @@ -682,9 +714,9 @@ func toTfStatusConfigV0(status *kbapi.SyntheticsStatusConfig) *tfStatusConfigV0 } } -func (v *tfModelV0) toKibanaAPIRequest() (*kibanaAPIRequest, diag.Diagnostics) { +func (v *tfModelV0) toKibanaAPIRequest(ctx context.Context) (*kibanaAPIRequest, diag.Diagnostics) { - fields, dg := v.toMonitorFields() + fields, dg := v.toMonitorFields(ctx) if dg.HasError() { return nil, dg } @@ -698,13 +730,13 @@ func (v *tfModelV0) toKibanaAPIRequest() (*kibanaAPIRequest, diag.Diagnostics) { }, dg } -func (v *tfModelV0) toMonitorFields() (kbapi.MonitorFields, diag.Diagnostics) { - var dg diag.Diagnostics +func (v *tfModelV0) toMonitorFields(ctx context.Context) (kbapi.MonitorFields, diag.Diagnostics) { + dg := diag.Diagnostics{} if v.HTTP != nil { - return v.toHttpMonitorFields() + return v.toHttpMonitorFields(ctx) } else if v.TCP != nil { - return v.toTCPMonitorFields(), dg + return v.toTCPMonitorFields(ctx) } else if v.ICMP != nil { return v.toICMPMonitorFields(), dg } else if v.Browser != nil { @@ -751,7 +783,7 @@ func tfInt64ToString(v types.Int64) string { return res } -func (v *tfModelV0) toHttpMonitorFields() (kbapi.MonitorFields, diag.Diagnostics) { +func (v *tfModelV0) toHttpMonitorFields(ctx context.Context) (kbapi.MonitorFields, diag.Diagnostics) { proxyHeaders, dg := toJsonObject(v.HTTP.ProxyHeader) if dg.HasError() { return nil, dg @@ -764,11 +796,17 @@ func (v *tfModelV0) toHttpMonitorFields() (kbapi.MonitorFields, diag.Diagnostics if dg.HasError() { return nil, dg } + + sslSupportedProtocols := utils.ListTypeToSlice_String(ctx, v.HTTP.SslSupportedProtocols, path.Root("http").AtName("ssl_supported_protocols"), dg) + if dg.HasError() { + return nil, dg + } + maxRedirects := tfInt64ToString(v.HTTP.MaxRedirects) return kbapi.HTTPMonitorFields{ Url: v.HTTP.URL.ValueString(), SslVerificationMode: v.HTTP.SslVerificationMode.ValueString(), - SslSupportedProtocols: ValueStringSlice(v.HTTP.SslSupportedProtocols), + SslSupportedProtocols: sslSupportedProtocols, MaxRedirects: maxRedirects, Mode: kbapi.HttpMonitorMode(v.HTTP.Mode.ValueString()), Ipv4: v.HTTP.IPv4.ValueBoolPointer(), @@ -779,19 +817,25 @@ func (v *tfModelV0) toHttpMonitorFields() (kbapi.MonitorFields, diag.Diagnostics ProxyUrl: v.HTTP.ProxyURL.ValueString(), Response: response, Check: check, - }, diag.Diagnostics{} //dg + }, dg } -func (v *tfModelV0) toTCPMonitorFields() kbapi.MonitorFields { +func (v *tfModelV0) toTCPMonitorFields(ctx context.Context) (kbapi.MonitorFields, diag.Diagnostics) { + dg := diag.Diagnostics{} + sslSupportedProtocols := utils.ListTypeToSlice_String(ctx, v.TCP.SslSupportedProtocols, path.Root("tcp").AtName("ssl_supported_protocols"), dg) + if dg.HasError() { + return nil, dg + } + return kbapi.TCPMonitorFields{ Host: v.TCP.Host.ValueString(), SslVerificationMode: v.TCP.SslVerificationMode.ValueString(), - SslSupportedProtocols: ValueStringSlice(v.TCP.SslSupportedProtocols), + SslSupportedProtocols: sslSupportedProtocols, CheckSend: v.TCP.CheckSend.ValueString(), CheckReceive: v.TCP.CheckReceive.ValueString(), ProxyUrl: v.TCP.ProxyURL.ValueString(), ProxyUseLocalResolver: v.TCP.ProxyUseLocalResolver.ValueBoolPointer(), - } + }, dg } func (v *tfModelV0) toICMPMonitorFields() kbapi.MonitorFields { diff --git a/internal/kibana/synthetics/schema_test.go b/internal/kibana/synthetics/schema_test.go index f91dbd2f8..dbd03bceb 100644 --- a/internal/kibana/synthetics/schema_test.go +++ b/internal/kibana/synthetics/schema_test.go @@ -1,11 +1,13 @@ package synthetics import ( + "context" "encoding/json" - "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "testing" "github.com/disaster37/go-kibana-rest/v8/kbapi" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stretchr/testify/assert" ) @@ -41,16 +43,17 @@ func TestToModelV0(t *testing.T) { TimeoutSeconds: types.Int64Value(0), Params: jsontypes.NewNormalizedValue("null"), HTTP: &tfHTTPMonitorFieldsV0{ - URL: types.StringValue(""), - SslVerificationMode: types.StringValue(""), - MaxRedirects: types.Int64Value(0), - Mode: types.StringValue(""), - Username: types.StringValue(""), - Password: types.StringValue(""), - ProxyHeader: jsontypes.NewNormalizedValue("null"), - ProxyURL: types.StringValue(""), - Response: jsontypes.NewNormalizedValue("null"), - Check: jsontypes.NewNormalizedValue("null"), + URL: types.StringValue(""), + SslVerificationMode: types.StringValue(""), + MaxRedirects: types.Int64Value(0), + Mode: types.StringValue(""), + Username: types.StringValue(""), + Password: types.StringValue(""), + ProxyHeader: jsontypes.NewNormalizedValue("null"), + ProxyURL: types.StringValue(""), + Response: jsontypes.NewNormalizedValue("null"), + Check: jsontypes.NewNormalizedValue("null"), + SslSupportedProtocols: types.ListNull(types.StringType), }, }, }, @@ -68,11 +71,12 @@ func TestToModelV0(t *testing.T) { TimeoutSeconds: types.Int64Value(0), Params: jsontypes.NewNormalizedValue("null"), TCP: &tfTCPMonitorFieldsV0{ - Host: types.StringValue(""), - SslVerificationMode: types.StringValue(""), - CheckSend: types.StringValue(""), - CheckReceive: types.StringValue(""), - ProxyURL: types.StringValue(""), + Host: types.StringValue(""), + SslVerificationMode: types.StringValue(""), + CheckSend: types.StringValue(""), + CheckReceive: types.StringValue(""), + ProxyURL: types.StringValue(""), + SslSupportedProtocols: types.ListNull(types.StringType), }, }, }, @@ -128,7 +132,7 @@ func TestToModelV0(t *testing.T) { APMServiceName: "test-service-http", Timeout: json.Number("30"), Locations: []kbapi.MonitorLocationConfig{ - {Label: "us_east", IsServiceManaged: true}, + {Label: "North America - US East", Id: "us_east", IsServiceManaged: true}, {Label: "test private location", IsServiceManaged: false}, }, Origin: "origin", @@ -171,17 +175,19 @@ func TestToModelV0(t *testing.T) { TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), HTTP: &tfHTTPMonitorFieldsV0{ - URL: types.StringValue("https://example.com"), - SslVerificationMode: types.StringValue("full"), - SslSupportedProtocols: []types.String{types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3")}, - MaxRedirects: types.Int64Value(5), - Mode: types.StringValue("all"), - IPv4: types.BoolPointerValue(tBool), - IPv6: types.BoolPointerValue(fBool), - Username: types.StringValue("user"), - Password: types.StringValue("pass"), - ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), - ProxyURL: types.StringValue("https://proxy.com"), + URL: types.StringValue("https://example.com"), + SslVerificationMode: types.StringValue("full"), + SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), + }), + MaxRedirects: types.Int64Value(5), + Mode: types.StringValue("all"), + IPv4: types.BoolPointerValue(tBool), + IPv6: types.BoolPointerValue(fBool), + Username: types.StringValue("user"), + Password: types.StringValue("pass"), + ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), + ProxyURL: types.StringValue("https://proxy.com"), }, }, }, @@ -228,9 +234,11 @@ func TestToModelV0(t *testing.T) { TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), TCP: &tfTCPMonitorFieldsV0{ - Host: types.StringValue("example.com:9200"), - SslVerificationMode: types.StringValue("full"), - SslSupportedProtocols: []types.String{types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3")}, + Host: types.StringValue("example.com:9200"), + SslVerificationMode: types.StringValue("full"), + SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), + }), CheckSend: types.StringValue("hello"), CheckReceive: types.StringValue("world"), ProxyURL: types.StringValue("http://proxy.com"), @@ -346,8 +354,9 @@ func TestToModelV0(t *testing.T) { for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { - model, err := tt.expected.toModelV0(&tt.input) - assert.NoError(t, err) + ctx := context.Background() + model, diag := tt.expected.toModelV0(ctx, &tt.input) + assert.False(t, diag.HasError()) assert.Equal(t, &tt.expected, model) }) } @@ -415,19 +424,21 @@ func TestToKibanaAPIRequest(t *testing.T) { TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), HTTP: &tfHTTPMonitorFieldsV0{ - URL: types.StringValue("https://example.com"), - SslVerificationMode: types.StringValue("full"), - SslSupportedProtocols: []types.String{types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3")}, - MaxRedirects: types.Int64Value(5), - Mode: types.StringValue("all"), - IPv4: types.BoolPointerValue(tBool), - IPv6: types.BoolPointerValue(fBool), - Username: types.StringValue("user"), - Password: types.StringValue("pass"), - ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), - ProxyURL: types.StringValue("https://proxy.com"), - Response: jsontypes.NewNormalizedValue(`{"response1":"value1"}`), - Check: jsontypes.NewNormalizedValue(`{"check1":"value1"}`), + URL: types.StringValue("https://example.com"), + SslVerificationMode: types.StringValue("full"), + SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), + }), + MaxRedirects: types.Int64Value(5), + Mode: types.StringValue("all"), + IPv4: types.BoolPointerValue(tBool), + IPv6: types.BoolPointerValue(fBool), + Username: types.StringValue("user"), + Password: types.StringValue("pass"), + ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), + ProxyURL: types.StringValue("https://proxy.com"), + Response: jsontypes.NewNormalizedValue(`{"response1":"value1"}`), + Check: jsontypes.NewNormalizedValue(`{"check1":"value1"}`), }, }, expected: kibanaAPIRequest{ @@ -477,9 +488,11 @@ func TestToKibanaAPIRequest(t *testing.T) { TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), TCP: &tfTCPMonitorFieldsV0{ - Host: types.StringValue("example.com:9200"), - SslVerificationMode: types.StringValue("full"), - SslSupportedProtocols: []types.String{types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3")}, + Host: types.StringValue("example.com:9200"), + SslVerificationMode: types.StringValue("full"), + SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), + }), CheckSend: types.StringValue("hello"), CheckReceive: types.StringValue("world"), ProxyURL: types.StringValue("http://proxy.com"), @@ -607,7 +620,7 @@ func TestToKibanaAPIRequest(t *testing.T) { for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { - apiRequest, dg := tt.input.toKibanaAPIRequest() + apiRequest, dg := tt.input.toKibanaAPIRequest(context.Background()) assert.False(t, dg.HasError(), dg.Errors()) assert.Equal(t, &tt.expected, apiRequest) }) @@ -648,16 +661,17 @@ func TestToModelV0MergeAttributes(t *testing.T) { Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), RetestOnFailure: types.BoolValue(true), HTTP: &tfHTTPMonitorFieldsV0{ - URL: types.StringValue(""), - SslVerificationMode: types.StringValue(""), - MaxRedirects: types.Int64Value(0), - Mode: types.StringValue(""), - ProxyURL: types.StringValue(""), - ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), - Username: types.StringValue("test"), - Password: types.StringValue("password"), - Check: jsontypes.NewNormalizedValue(`{"check1":"value1"}`), - Response: jsontypes.NewNormalizedValue(`{"response1":"value1"}`), + URL: types.StringValue(""), + SslVerificationMode: types.StringValue(""), + SslSupportedProtocols: types.ListNull(types.StringType), + MaxRedirects: types.Int64Value(0), + Mode: types.StringValue(""), + ProxyURL: types.StringValue(""), + ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), + Username: types.StringValue("test"), + Password: types.StringValue("password"), + Check: jsontypes.NewNormalizedValue(`{"check1":"value1"}`), + Response: jsontypes.NewNormalizedValue(`{"response1":"value1"}`), }, }, }, @@ -680,11 +694,12 @@ func TestToModelV0MergeAttributes(t *testing.T) { APMServiceName: types.StringValue(""), TimeoutSeconds: types.Int64Value(0), TCP: &tfTCPMonitorFieldsV0{ - Host: types.StringValue(""), - SslVerificationMode: types.StringValue(""), - CheckSend: types.StringValue("hello"), - CheckReceive: types.StringValue("world"), - ProxyURL: types.StringValue(""), + Host: types.StringValue(""), + SslVerificationMode: types.StringValue(""), + CheckSend: types.StringValue("hello"), + CheckReceive: types.StringValue("world"), + ProxyURL: types.StringValue(""), + SslSupportedProtocols: types.ListNull(types.StringType), }, }, }, @@ -717,8 +732,9 @@ func TestToModelV0MergeAttributes(t *testing.T) { for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { - actual, err := tt.state.toModelV0(&tt.input) - assert.NoError(t, err) + ctx := context.Background() + actual, diag := tt.state.toModelV0(ctx, &tt.input) + assert.False(t, diag.HasError()) assert.NotNil(t, actual) assert.Equal(t, &tt.expected, actual) }) diff --git a/internal/kibana/synthetics/update.go b/internal/kibana/synthetics/update.go index bcd66b914..c7544622b 100644 --- a/internal/kibana/synthetics/update.go +++ b/internal/kibana/synthetics/update.go @@ -21,7 +21,7 @@ func (r *Resource) Update(ctx context.Context, request resource.UpdateRequest, r return } - input, diags := plan.toKibanaAPIRequest() + input, diags := plan.toKibanaAPIRequest(ctx) response.Diagnostics.Append(diags...) if response.Diagnostics.HasError() { return @@ -40,9 +40,9 @@ func (r *Resource) Update(ctx context.Context, request resource.UpdateRequest, r return } - plan, err = plan.toModelV0(result) - if err != nil { - response.Diagnostics.AddError("Failed to convert Kibana monitor API to TF state", err.Error()) + plan, diags = plan.toModelV0(ctx, result) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { return }