internal/kibana/synthetics/schema.go (957 lines of code) (raw):

package synthetics import ( "context" "encoding/json" "fmt" "strconv" "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/attr" "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/objectplanmodifier" "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/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) const ( MetadataPrefix = "_kibana_synthetics_" ) type kibanaAPIRequest struct { fields kbapi.MonitorFields config kbapi.SyntheticsMonitorConfig } type tfStatusConfigV0 struct { Enabled types.Bool `tfsdk:"enabled"` } type tfAlertConfigV0 struct { Status *tfStatusConfigV0 `tfsdk:"status"` TLS *tfStatusConfigV0 `tfsdk:"tls"` } type tfSSLConfig struct { SslVerificationMode types.String `tfsdk:"ssl_verification_mode"` SslSupportedProtocols types.List `tfsdk:"ssl_supported_protocols"` SslCertificateAuthorities []types.String `tfsdk:"ssl_certificate_authorities"` SslCertificate types.String `tfsdk:"ssl_certificate"` SslKey types.String `tfsdk:"ssl_key"` SslKeyPassphrase types.String `tfsdk:"ssl_key_passphrase"` } type tfHTTPMonitorFieldsV0 struct { URL types.String `tfsdk:"url"` MaxRedirects types.Int64 `tfsdk:"max_redirects"` Mode types.String `tfsdk:"mode"` IPv4 types.Bool `tfsdk:"ipv4"` IPv6 types.Bool `tfsdk:"ipv6"` ProxyURL types.String `tfsdk:"proxy_url"` ProxyHeader jsontypes.Normalized `tfsdk:"proxy_header"` Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` Response jsontypes.Normalized `tfsdk:"response"` Check jsontypes.Normalized `tfsdk:"check"` tfSSLConfig } type tfTCPMonitorFieldsV0 struct { Host types.String `tfsdk:"host"` 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"` tfSSLConfig } type tfICMPMonitorFieldsV0 struct { Host types.String `tfsdk:"host"` Wait types.Int64 `tfsdk:"wait"` } type tfBrowserMonitorFieldsV0 struct { InlineScript types.String `tfsdk:"inline_script"` Screenshots types.String `tfsdk:"screenshots"` SyntheticsArgs []types.String `tfsdk:"synthetics_args"` IgnoreHttpsErrors types.Bool `tfsdk:"ignore_https_errors"` PlaywrightOptions jsontypes.Normalized `tfsdk:"playwright_options"` } type tfModelV0 struct { ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` SpaceID types.String `tfsdk:"space_id"` Schedule types.Int64 `tfsdk:"schedule"` Locations []types.String `tfsdk:"locations"` PrivateLocations []types.String `tfsdk:"private_locations"` Enabled types.Bool `tfsdk:"enabled"` Tags []types.String `tfsdk:"tags"` Alert types.Object `tfsdk:"alert"` //tfAlertConfigV0 APMServiceName types.String `tfsdk:"service_name"` TimeoutSeconds types.Int64 `tfsdk:"timeout"` HTTP *tfHTTPMonitorFieldsV0 `tfsdk:"http"` TCP *tfTCPMonitorFieldsV0 `tfsdk:"tcp"` ICMP *tfICMPMonitorFieldsV0 `tfsdk:"icmp"` Browser *tfBrowserMonitorFieldsV0 `tfsdk:"browser"` Params jsontypes.Normalized `tfsdk:"params"` RetestOnFailure types.Bool `tfsdk:"retest_on_failure"` } func GetCompositeId(id string) (*clients.CompositeId, diag.Diagnostics) { compositeID, sdkDiag := clients.CompositeIdFromStr(id) dg := diag.Diagnostics{} if sdkDiag.HasError() { dg.AddError(fmt.Sprintf("Failed to parse monitor ID %s", id), fmt.Sprintf("Resource ID must have following format: <cluster_uuid>/<resource identifier>. Current value: %s", id)) return nil, dg } return compositeID, dg } func monitorConfigSchema() schema.Schema { return schema.Schema{ MarkdownDescription: "Synthetics monitor config, see https://www.elastic.co/guide/en/kibana/current/add-monitor-api.html for more details. The monitor must have one of the following: http, tcp, icmp or browser.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, MarkdownDescription: "Generated identifier for the monitor", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), stringplanmodifier.RequiresReplace(), }, }, "name": schema.StringAttribute{ Optional: false, Required: true, MarkdownDescription: "The monitor’s name.", }, "space_id": schema.StringAttribute{ MarkdownDescription: "The namespace field should be lowercase and not contain spaces. The namespace must not include any of the following characters: *, \\, /, ?, \", <, >, |, whitespace, ,, #, :, or -. Default: `default`", Optional: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), stringplanmodifier.RequiresReplace(), }, Computed: true, }, "schedule": schema.Int64Attribute{ Optional: true, MarkdownDescription: "The monitor’s schedule in minutes. Supported values are 1, 3, 5, 10, 15, 30, 60, 120 and 240.", Validators: []validator.Int64{ int64validator.OneOf(1, 3, 5, 10, 15, 30, 60, 120, 240), }, Computed: true, PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, }, "locations": schema.ListAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "Where to deploy the monitor. Monitors can be deployed in multiple locations so that you can detect differences in availability and response times across those locations.", Validators: []validator.List{ listvalidator.ValueStringsAre( stringvalidator.OneOf( "japan", "india", "singapore", "australia_east", "united_kingdom", "germany", "canada_east", "brazil", "us_east", "us_west", ), ), }, }, "private_locations": schema.ListAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "These Private Locations refer to locations hosted and managed by you, whereas locations are hosted by Elastic. You can specify a Private Location using the location’s name.", }, "enabled": schema.BoolAttribute{ Optional: true, MarkdownDescription: "Whether the monitor is enabled. Default: `true`", Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, "tags": schema.ListAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "An array of tags.", }, "alert": monitorAlertConfigSchema(), "service_name": schema.StringAttribute{ Optional: true, MarkdownDescription: "The APM service name.", Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "timeout": schema.Int64Attribute{ Optional: true, MarkdownDescription: "The monitor timeout in seconds, monitor will fail if it doesn’t complete within this time. Default: `16`", Computed: true, PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, }, "params": jsonObjectSchema("Monitor parameters"), "http": httpMonitorFieldsSchema(), "tcp": tcpMonitorFieldsSchema(), "icmp": icmpMonitorFieldsSchema(), "browser": browserMonitorFieldsSchema(), "retest_on_failure": schema.BoolAttribute{ Optional: true, MarkdownDescription: "Enable or disable retesting when a monitor fails. By default, monitors are automatically retested if the monitor goes from \"up\" to \"down\". If the result of the retest is also \"down\", an error will be created, and if configured, an alert sent. Then the monitor will resume running according to the defined schedule. Using retest_on_failure can reduce noise related to transient problems. Default: `true`.", }, }, } } func browserMonitorFieldsSchema() schema.Attribute { return schema.SingleNestedAttribute{ Optional: true, MarkdownDescription: "Browser Monitor specific fields", Attributes: map[string]schema.Attribute{ "inline_script": schema.StringAttribute{ Optional: false, Required: true, MarkdownDescription: "The inline script.", }, "screenshots": schema.StringAttribute{ Optional: true, MarkdownDescription: "Controls the behavior of the screenshots feature.", Validators: []validator.String{ stringvalidator.OneOf("on", "off", "only-on-failure"), }, Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "synthetics_args": schema.ListAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "Synthetics agent CLI arguments.", }, "ignore_https_errors": schema.BoolAttribute{ Optional: true, MarkdownDescription: "Whether to ignore HTTPS errors.", Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, "playwright_options": jsonObjectSchema("Playwright options."), }, } } func icmpMonitorFieldsSchema() schema.Attribute { return schema.SingleNestedAttribute{ Optional: true, MarkdownDescription: "ICMP Monitor specific fields", Attributes: map[string]schema.Attribute{ "host": schema.StringAttribute{ Optional: false, Required: true, MarkdownDescription: "Host to ping; it can be an IP address or a hostname.", }, "wait": schema.Int64Attribute{ Optional: true, MarkdownDescription: " Wait time in seconds. Default: `1`", PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, Computed: true, }, }, } } func jsonObjectSchema(doc string) schema.Attribute { return schema.StringAttribute{ Optional: true, MarkdownDescription: fmt.Sprintf("%s. Raw JSON object, use `jsonencode` function to represent JSON", doc), CustomType: jsontypes.NormalizedType{}, } } func statusConfigSchema() schema.Attribute { return schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "enabled": schema.BoolAttribute{ Optional: true, Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, }, } } func monitorAlertConfigSchema() schema.Attribute { return schema.SingleNestedAttribute{ Optional: true, MarkdownDescription: "Alert configuration. Default: `{ status: { enabled: true }, tls: { enabled: true } }`.", Attributes: map[string]schema.Attribute{ "status": statusConfigSchema(), "tls": statusConfigSchema(), }, Computed: true, PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()}, } } func httpMonitorFieldsSchema() schema.Attribute { return schema.SingleNestedAttribute{ Optional: true, MarkdownDescription: "HTTP Monitor specific fields", Attributes: map[string]schema.Attribute{ "url": schema.StringAttribute{ Optional: false, Required: true, MarkdownDescription: "URL to monitor.", }, "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()}, }, "ssl_certificate_authorities": schema.ListAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "The list of root certificates for verifications is required.", }, "ssl_certificate": schema.StringAttribute{ Optional: true, MarkdownDescription: "Certificate.", Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "ssl_key": schema.StringAttribute{ Optional: true, MarkdownDescription: "Certificate key.", Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, Sensitive: true, }, "ssl_key_passphrase": schema.StringAttribute{ Optional: true, MarkdownDescription: "Key passphrase.", Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, Sensitive: true, }, "max_redirects": schema.Int64Attribute{ Optional: true, MarkdownDescription: "The maximum number of redirects to follow. Default: `0`", PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, Computed: true, }, "mode": schema.StringAttribute{ Optional: true, MarkdownDescription: "The mode of the monitor. Can be \"all\" or \"any\". If you’re using a DNS-load balancer and want to ping every IP address for the specified hostname, you should use all.", Validators: []validator.String{ stringvalidator.OneOf("any", "all"), }, Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "ipv4": schema.BoolAttribute{ Optional: true, MarkdownDescription: "Whether to ping using the ipv4 protocol.", Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, "ipv6": schema.BoolAttribute{ Optional: true, MarkdownDescription: "Whether to ping using the ipv6 protocol.", Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, "username": schema.StringAttribute{ Optional: true, MarkdownDescription: "The username for authenticating with the server. The credentials are passed with the request.", }, "password": schema.StringAttribute{ Optional: true, MarkdownDescription: "The password for authenticating with the server. The credentials are passed with the request.", }, "proxy_header": jsonObjectSchema("Additional headers to send to proxies during CONNECT requests."), "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."), }, } } func tcpMonitorFieldsSchema() schema.Attribute { return schema.SingleNestedAttribute{ Optional: true, MarkdownDescription: "TCP Monitor specific fields", Attributes: map[string]schema.Attribute{ "host": schema.StringAttribute{ Optional: false, Required: true, MarkdownDescription: "The host to monitor; it can be an IP address or a hostname. The host can include the port using a colon (e.g., \"example.com:9200\").", }, "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()}, }, "ssl_certificate_authorities": schema.ListAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "The list of root certificates for verifications is required.", }, "ssl_certificate": schema.StringAttribute{ Optional: true, MarkdownDescription: "Certificate.", Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "ssl_key": schema.StringAttribute{ Optional: true, MarkdownDescription: "Certificate key.", Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, Sensitive: true, }, "ssl_key_passphrase": schema.StringAttribute{ Optional: true, MarkdownDescription: "Key passphrase.", Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, Sensitive: true, }, "check_send": schema.StringAttribute{ Optional: true, MarkdownDescription: "An optional payload string to send to the remote host.", }, "check_receive": schema.StringAttribute{ Optional: true, MarkdownDescription: "The expected answer. ", }, "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, MarkdownDescription: " A Boolean value that determines whether hostnames are resolved locally instead of being resolved on the proxy server. The default value is false, which means that name resolution occurs on the proxy server.", Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, }, } } func GeoConfigSchema() schema.Attribute { return schema.SingleNestedAttribute{ Optional: true, Description: "Geographic coordinates (WGS84) for the location", Attributes: map[string]schema.Attribute{ "lat": schema.Float64Attribute{ Optional: false, Required: true, MarkdownDescription: "The latitude of the location.", }, "lon": schema.Float64Attribute{ Optional: false, Required: true, MarkdownDescription: "The longitude of the location.", }, }, } } type TFGeoConfigV0 struct { Lat types.Float64 `tfsdk:"lat"` Lon types.Float64 `tfsdk:"lon"` } func (m *TFGeoConfigV0) ToSyntheticGeoConfig() *kbapi.SyntheticGeoConfig { return &kbapi.SyntheticGeoConfig{ Lat: m.Lat.ValueFloat64(), Lon: m.Lon.ValueFloat64(), } } func FromSyntheticGeoConfig(v *kbapi.SyntheticGeoConfig) *TFGeoConfigV0 { if v == nil { return nil } return &TFGeoConfigV0{ Lat: types.Float64Value(v.Lat), Lon: types.Float64Value(v.Lon), } } func ValueStringSlice(v []types.String) []string { var res []string for _, s := range v { res = append(res, s.ValueString()) } return res } func StringSliceValue(v []string) []types.String { var res []types.String for _, s := range v { res = append(res, types.StringValue(s)) } return res } func toNormalizedValue(jsObj kbapi.JsonObject) (jsontypes.Normalized, error) { res, err := json.Marshal(jsObj) if err != nil { return jsontypes.NewNormalizedUnknown(), err } return jsontypes.NewNormalizedValue(string(res)), nil } func toJsonObject(v jsontypes.Normalized) (kbapi.JsonObject, diag.Diagnostics) { if v.IsNull() { return nil, diag.Diagnostics{} } var res kbapi.JsonObject dg := v.Unmarshal(&res) if dg.HasError() { return nil, dg } return res, diag.Diagnostics{} } func stringToInt64(v string) (int64, error) { var res int64 var err error if v != "" { res, err = strconv.ParseInt(v, 10, 64) } return res, err } 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 { dg.AddError("Failed to convert schedule to int64", err.Error()) return nil, dg } } var privateLocLabels []string for _, l := range api.Locations { if !l.IsServiceManaged { privateLocLabels = append(privateLocLabels, l.Label) } } timeout, err := stringToInt64(string(api.Timeout)) if err != nil { dg.AddError("Failed to convert timeout to int64", err.Error()) return nil, dg } var http *tfHTTPMonitorFieldsV0 var tcp *tfTCPMonitorFieldsV0 var icmp *tfICMPMonitorFieldsV0 var browser *tfBrowserMonitorFieldsV0 switch mType := api.Type; mType { case kbapi.Http: http = &tfHTTPMonitorFieldsV0{} if v.HTTP != nil { http = v.HTTP } http = http.toTfHTTPMonitorFieldsV0(ctx, dg, api) case kbapi.Tcp: tcp = &tfTCPMonitorFieldsV0{} if v.TCP != nil { tcp = v.TCP } tcp = tcp.toTfTCPMonitorFieldsV0(ctx, dg, api) case kbapi.Icmp: icmp = &tfICMPMonitorFieldsV0{} if v.ICMP != nil { icmp = v.ICMP } icmp, err = icmp.toTfICMPMonitorFieldsV0(api) case kbapi.Browser: browser = &tfBrowserMonitorFieldsV0{} if v.Browser != nil { browser = v.Browser } browser, err = browser.toTfBrowserMonitorFieldsV0(api) default: err = fmt.Errorf("unsupported monitor type: %s", mType) } if err != nil { 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 { dg.AddError("Failed to parse params", err.Error()) return nil, dg } } resourceID := clients.CompositeId{ ClusterId: api.Namespace, ResourceId: string(api.Id), } alertV0, dg := toTfAlertConfigV0(ctx, api.Alert) if dg.HasError() { return nil, dg } return &tfModelV0{ ID: types.StringValue(resourceID.String()), Name: types.StringValue(api.Name), SpaceID: types.StringValue(api.Namespace), Schedule: types.Int64Value(schedule), Locations: v.Locations, PrivateLocations: StringSliceValue(privateLocLabels), Enabled: types.BoolPointerValue(api.Enabled), Tags: StringSliceValue(api.Tags), Alert: alertV0, APMServiceName: types.StringValue(api.APMServiceName), TimeoutSeconds: types.Int64Value(timeout), Params: params, HTTP: http, TCP: tcp, ICMP: icmp, Browser: browser, RetestOnFailure: v.RetestOnFailure, }, dg } 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) } checkReceive := v.CheckReceive if api.CheckReceive != "" { checkReceive = types.StringValue(api.CheckReceive) } sslCfg, dg := toTFSSLConfig(ctx, dg, api, "tcp") if dg.HasError() { return nil } return &tfTCPMonitorFieldsV0{ Host: types.StringValue(api.Host), CheckSend: checkSend, CheckReceive: checkReceive, ProxyURL: types.StringValue(api.ProxyUrl), ProxyUseLocalResolver: types.BoolPointerValue(api.ProxyUseLocalResolver), tfSSLConfig: sslCfg, } } func (v *tfICMPMonitorFieldsV0) toTfICMPMonitorFieldsV0(api *kbapi.SyntheticsMonitor) (*tfICMPMonitorFieldsV0, error) { wait, err := stringToInt64(string(api.Wait)) if err != nil { return nil, err } return &tfICMPMonitorFieldsV0{ Host: types.StringValue(api.Host), Wait: types.Int64Value(wait), }, nil } func (v *tfBrowserMonitorFieldsV0) toTfBrowserMonitorFieldsV0(api *kbapi.SyntheticsMonitor) (*tfBrowserMonitorFieldsV0, error) { var err error playwrightOptions := v.PlaywrightOptions if api.PlaywrightOptions != nil { playwrightOptions, err = toNormalizedValue(api.PlaywrightOptions) if err != nil { return nil, err } } syntheticsArgs := v.SyntheticsArgs if api.SyntheticsArgs != nil { syntheticsArgs = StringSliceValue(api.SyntheticsArgs) } inlineScript := v.InlineScript if api.InlineScript != "" { inlineScript = types.StringValue(api.InlineScript) } return &tfBrowserMonitorFieldsV0{ InlineScript: inlineScript, Screenshots: types.StringValue(api.Screenshots), SyntheticsArgs: syntheticsArgs, IgnoreHttpsErrors: types.BoolPointerValue(api.IgnoreHttpsErrors), PlaywrightOptions: playwrightOptions, }, nil } 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 { dg.AddError("Failed to parse proxy_headers", err.Error()) return nil } } username := v.Username if api.Username != "" { username = types.StringValue(api.Username) } password := v.Password if api.Password != "" { password = types.StringValue(api.Password) } maxRedirects, err := stringToInt64(api.MaxRedirects) if err != nil { dg.AddError("Failed to parse max_redirects", err.Error()) return nil } sslCfg, dg := toTFSSLConfig(ctx, dg, api, "http") if dg.HasError() { return nil } return &tfHTTPMonitorFieldsV0{ URL: types.StringValue(api.Url), MaxRedirects: types.Int64Value(maxRedirects), Mode: types.StringValue(string(api.Mode)), IPv4: types.BoolPointerValue(api.Ipv4), IPv6: types.BoolPointerValue(api.Ipv6), Username: username, Password: password, ProxyHeader: proxyHeaders, ProxyURL: types.StringValue(api.ProxyUrl), Check: v.Check, Response: v.Response, tfSSLConfig: sslCfg, } } func toTFSSLConfig(ctx context.Context, dg diag.Diagnostics, api *kbapi.SyntheticsMonitor, p string) (tfSSLConfig, diag.Diagnostics) { sslSupportedProtocols := utils.SliceToListType_String(ctx, api.SslSupportedProtocols, path.Root(p).AtName("ssl_supported_protocols"), &dg) return tfSSLConfig{ SslVerificationMode: types.StringValue(api.SslVerificationMode), SslSupportedProtocols: sslSupportedProtocols, SslCertificateAuthorities: StringSliceValue(api.SslCertificateAuthorities), SslCertificate: types.StringValue(api.SslCertificate), SslKey: types.StringValue(api.SslKey), SslKeyPassphrase: types.StringValue(api.SslKeyPassphrase), }, dg } func toTfAlertConfigV0(ctx context.Context, alert *kbapi.MonitorAlertConfig) (basetypes.ObjectValue, diag.Diagnostics) { dg := diag.Diagnostics{} alertAttributes := monitorAlertConfigSchema().GetType().(attr.TypeWithAttributeTypes).AttributeTypes() var emptyAttr = map[string]attr.Type(nil) if alert == nil { return basetypes.NewObjectNull(emptyAttr), dg } tfAlertConfig := tfAlertConfigV0{ Status: toTfStatusConfigV0(alert.Status), TLS: toTfStatusConfigV0(alert.Tls), } return types.ObjectValueFrom(ctx, alertAttributes, &tfAlertConfig) } func toTfStatusConfigV0(status *kbapi.SyntheticsStatusConfig) *tfStatusConfigV0 { if status == nil { return nil } return &tfStatusConfigV0{ Enabled: types.BoolPointerValue(status.Enabled), } } func (v *tfModelV0) toKibanaAPIRequest(ctx context.Context) (*kibanaAPIRequest, diag.Diagnostics) { fields, dg := v.toMonitorFields(ctx) if dg.HasError() { return nil, dg } config, dg := v.toSyntheticsMonitorConfig(ctx) if dg.HasError() { return nil, dg } return &kibanaAPIRequest{ fields: fields, config: *config, }, dg } func (v *tfModelV0) toMonitorFields(ctx context.Context) (kbapi.MonitorFields, diag.Diagnostics) { dg := diag.Diagnostics{} if v.HTTP != nil { return v.toHttpMonitorFields(ctx) } else if v.TCP != nil { return v.toTCPMonitorFields(ctx) } else if v.ICMP != nil { return v.toICMPMonitorFields(), dg } else if v.Browser != nil { return v.toBrowserMonitorFields() } dg.AddError("Unsupported monitor type config", "one of http,tcp monitor fields is required") return nil, dg } func toTFAlertConfig(ctx context.Context, v basetypes.ObjectValue) *kbapi.MonitorAlertConfig { var alert *kbapi.MonitorAlertConfig if !v.IsNull() && !v.IsUnknown() { tfAlert := tfAlertConfigV0{} tfsdk.ValueAs(ctx, v, &tfAlert) alert = tfAlert.toTfAlertConfigV0() } return alert } func (v *tfModelV0) toSyntheticsMonitorConfig(ctx context.Context) (*kbapi.SyntheticsMonitorConfig, diag.Diagnostics) { locations := Map[types.String, kbapi.MonitorLocation](v.Locations, func(s types.String) kbapi.MonitorLocation { return kbapi.MonitorLocation(s.ValueString()) }) params, dg := toJsonObject(v.Params) if dg.HasError() { return nil, dg } return &kbapi.SyntheticsMonitorConfig{ Name: v.Name.ValueString(), Schedule: kbapi.MonitorSchedule(v.Schedule.ValueInt64()), Locations: locations, PrivateLocations: ValueStringSlice(v.PrivateLocations), Enabled: v.Enabled.ValueBoolPointer(), Tags: ValueStringSlice(v.Tags), Alert: toTFAlertConfig(ctx, v.Alert), APMServiceName: v.APMServiceName.ValueString(), TimeoutSeconds: int(v.TimeoutSeconds.ValueInt64()), Namespace: v.SpaceID.ValueString(), Params: params, RetestOnFailure: v.RetestOnFailure.ValueBoolPointer(), }, diag.Diagnostics{} //dg } func tfInt64ToString(v types.Int64) string { res := "" if !v.IsUnknown() && !v.IsNull() { // handle omitempty case return strconv.FormatInt(v.ValueInt64(), 10) } return res } func toSSLConfig(ctx context.Context, dg diag.Diagnostics, v tfSSLConfig, p string) (*kbapi.SSLConfig, diag.Diagnostics) { var ssl *kbapi.SSLConfig if !v.SslSupportedProtocols.IsNull() && !v.SslSupportedProtocols.IsUnknown() { sslSupportedProtocols := utils.ListTypeToSlice_String(ctx, v.SslSupportedProtocols, path.Root(p).AtName("ssl_supported_protocols"), &dg) if dg.HasError() { return nil, dg } ssl = &kbapi.SSLConfig{} ssl.SupportedProtocols = sslSupportedProtocols } if !v.SslVerificationMode.IsNull() && !v.SslVerificationMode.IsUnknown() { if ssl == nil { ssl = &kbapi.SSLConfig{} } ssl.VerificationMode = v.SslVerificationMode.ValueString() } certAuths := ValueStringSlice(v.SslCertificateAuthorities) if len(certAuths) > 0 { if ssl == nil { ssl = &kbapi.SSLConfig{} } ssl.CertificateAuthorities = certAuths } if !v.SslCertificate.IsUnknown() && !v.SslCertificate.IsNull() { if ssl == nil { ssl = &kbapi.SSLConfig{} } ssl.Certificate = v.SslCertificate.ValueString() } if !v.SslKey.IsUnknown() && !v.SslKey.IsNull() { if ssl == nil { ssl = &kbapi.SSLConfig{} } ssl.Key = v.SslKey.ValueString() } if !v.SslKeyPassphrase.IsUnknown() && !v.SslKeyPassphrase.IsNull() { if ssl == nil { ssl = &kbapi.SSLConfig{} } ssl.KeyPassphrase = v.SslKeyPassphrase.ValueString() } return ssl, dg } func (v *tfModelV0) toHttpMonitorFields(ctx context.Context) (kbapi.MonitorFields, diag.Diagnostics) { http := v.HTTP proxyHeaders, dg := toJsonObject(http.ProxyHeader) if dg.HasError() { return nil, dg } response, dg := toJsonObject(http.Response) if dg.HasError() { return nil, dg } check, dg := toJsonObject(http.Check) if dg.HasError() { return nil, dg } ssl, dg := toSSLConfig(ctx, dg, http.tfSSLConfig, "http") maxRedirects := tfInt64ToString(http.MaxRedirects) return kbapi.HTTPMonitorFields{ Url: http.URL.ValueString(), Ssl: ssl, MaxRedirects: maxRedirects, Mode: kbapi.HttpMonitorMode(http.Mode.ValueString()), Ipv4: http.IPv4.ValueBoolPointer(), Ipv6: http.IPv6.ValueBoolPointer(), Username: http.Username.ValueString(), Password: http.Password.ValueString(), ProxyHeader: proxyHeaders, ProxyUrl: http.ProxyURL.ValueString(), Response: response, Check: check, }, dg } func (v *tfModelV0) toTCPMonitorFields(ctx context.Context) (kbapi.MonitorFields, diag.Diagnostics) { tcp := v.TCP dg := diag.Diagnostics{} ssl, dg := toSSLConfig(ctx, dg, tcp.tfSSLConfig, "tcp") return kbapi.TCPMonitorFields{ Host: tcp.Host.ValueString(), CheckSend: tcp.CheckSend.ValueString(), CheckReceive: tcp.CheckReceive.ValueString(), ProxyUrl: tcp.ProxyURL.ValueString(), ProxyUseLocalResolver: tcp.ProxyUseLocalResolver.ValueBoolPointer(), Ssl: ssl, }, dg } func (v *tfModelV0) toICMPMonitorFields() kbapi.MonitorFields { return kbapi.ICMPMonitorFields{ Host: v.ICMP.Host.ValueString(), Wait: tfInt64ToString(v.ICMP.Wait), } } func (v *tfModelV0) toBrowserMonitorFields() (kbapi.MonitorFields, diag.Diagnostics) { playwrightOptions, dg := toJsonObject(v.Browser.PlaywrightOptions) if dg.HasError() { return nil, dg } return kbapi.BrowserMonitorFields{ InlineScript: v.Browser.InlineScript.ValueString(), Screenshots: kbapi.ScreenshotOption(v.Browser.Screenshots.ValueString()), SyntheticsArgs: ValueStringSlice(v.Browser.SyntheticsArgs), IgnoreHttpsErrors: v.Browser.IgnoreHttpsErrors.ValueBoolPointer(), PlaywrightOptions: playwrightOptions, }, diag.Diagnostics{} //dg } func Map[T, U any](ts []T, f func(T) U) []U { var us []U for _, v := range ts { us = append(us, f(v)) } return us } func (v tfAlertConfigV0) toTfAlertConfigV0() *kbapi.MonitorAlertConfig { var status *kbapi.SyntheticsStatusConfig if v.Status != nil { status = v.Status.toTfStatusConfigV0() } var tls *kbapi.SyntheticsStatusConfig if v.TLS != nil { tls = v.TLS.toTfStatusConfigV0() } return &kbapi.MonitorAlertConfig{ Status: status, Tls: tls, } } func (v tfStatusConfigV0) toTfStatusConfigV0() *kbapi.SyntheticsStatusConfig { return &kbapi.SyntheticsStatusConfig{ Enabled: v.Enabled.ValueBoolPointer(), } }