diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 747672a3c..77bdff3aa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -128,12 +128,12 @@ jobs:
- '8.13.4'
- '8.14.3'
- '8.15.5'
- - '8.16.2'
- - '8.17.10'
- - '8.18.7'
- - '8.19.3'
- - '9.0.7'
- - '9.1.3'
+ - '8.16.6'
+ - '8.17.1'
+ - '8.18.8'
+ - '8.19.8'
+ - '9.0.8'
+ - '9.1.8'
# The default runner image can be overridden for specific versions if needed
# include:
# - version: '8.0.1'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 33e50001a..a30d35fa5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
## [Unreleased]
+### Added
+
+- Add `advanced_settings` to `elasticstack_fleet_agent_policy` to configure agent logging, CPU limits, and download settings ([#1545](https://github.com/elastic/terraform-provider-elasticstack/pull/1545))
+
### Breaking changes
#### `elasticstack_fleet_integration_policy` input block has changed to a map attribute.
diff --git a/Makefile b/Makefile
index c1bea9c8f..c455cdfc6 100644
--- a/Makefile
+++ b/Makefile
@@ -92,6 +92,10 @@ docker-fleet: ## Start Fleet node in docker container
set-kibana-password: ## Sets the ES KIBANA_SYSTEM_USERNAME's password to KIBANA_SYSTEM_PASSWORD. This expects Elasticsearch to be available at localhost:9200
@ curl $(CURL_OPTS) http://localhost:9200/_security/user/$(KIBANA_SYSTEM_USERNAME)/_password -d '{"password":"$(KIBANA_SYSTEM_PASSWORD)"}'
+.PHONY: setup-synthetics
+setup-synthetics: ## Creates the synthetics policy required to run Synthetics. This expects Kibana to be available at localhost:5601
+ @ curl $(CURL_OPTS) -H "kbn-xsrf: true" http://localhost:5601/api/fleet/epm/packages/synthetics/1.2.2 -d '{"force": true}'
+
.PHONY: create-es-api-key
create-es-api-key: ## Creates and outputs a new API Key. This expects Elasticsearch to be available at localhost:9200
@ curl $(CURL_OPTS) http://localhost:9200/_security/api_key -d '{"name":"$(KIBANA_API_KEY_NAME)"}'
diff --git a/docs/resources/fleet_agent_policy.md b/docs/resources/fleet_agent_policy.md
index 3ba2483c2..ac255526b 100644
--- a/docs/resources/fleet_agent_policy.md
+++ b/docs/resources/fleet_agent_policy.md
@@ -74,6 +74,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" {
### Optional
- `advanced_monitoring_options` (Attributes) Advanced monitoring options for the agent policy. Includes HTTP monitoring endpoint configuration and diagnostic settings. (see [below for nested schema](#nestedatt--advanced_monitoring_options))
+- `advanced_settings` (Attributes) Advanced agent settings for logging, resource limits, and downloads. These settings configure the behavior of Elastic Agents enrolled in this policy. (see [below for nested schema](#nestedatt--advanced_settings))
- `data_output_id` (String) The identifier for the data output.
- `description` (String) The description of the agent policy.
- `download_source_id` (String) The identifier for the Elastic Agent binary download server.
@@ -145,6 +146,23 @@ Optional:
+
+### Nested Schema for `advanced_settings`
+
+Optional:
+
+- `download_target_directory` (String) Target directory for downloading agent updates.
+- `download_timeout` (String) Timeout for downloading agent updates (e.g., '2h', '30m').
+- `go_max_procs` (Number) Maximum number of CPUs that the agent can use (GOMAXPROCS). Set to 0 to use all available CPUs.
+- `logging_files_interval` (String) Interval for log file rotation (e.g., '30s', '1m', '1h').
+- `logging_files_keepfiles` (Number) Number of rotated log files to keep.
+- `logging_files_rotateeverybytes` (Number) Rotate log files when they reach this size in bytes.
+- `logging_level` (String) Logging level for the agent. Valid values: debug, info, warning, error.
+- `logging_metrics_period` (String) Period for logging agent metrics (e.g., '30s', '1m').
+- `logging_to_files` (Boolean) Enable logging to files.
+- `monitoring_runtime_experimental` (String) Experimental runtime monitoring mode. Valid values: '' (empty string to disable), 'process', 'otel'.
+
+
### Nested Schema for `global_data_tags`
diff --git a/go.mod b/go.mod
index f25861024..1826f2977 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/elastic/terraform-provider-elasticstack
-go 1.25.1
+go 1.25.4
require (
github.com/disaster37/go-kibana-rest/v8 v8.5.0
diff --git a/internal/fleet/agent_policy/acc_test.go b/internal/fleet/agent_policy/acc_test.go
index aab995593..2320e12fc 100644
--- a/internal/fleet/agent_policy/acc_test.go
+++ b/internal/fleet/agent_policy/acc_test.go
@@ -663,6 +663,102 @@ func TestAccResourceAgentPolicyWithRequiredVersions(t *testing.T) {
})
}
+func TestAccResourceAgentPolicyWithAdvancedSettings(t *testing.T) {
+ policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ CheckDestroy: checkResourceAgentPolicyDestroy,
+ Steps: []resource.TestStep{
+ // Step 1: Create with logging settings
+ {
+ ProtoV6ProviderFactories: acctest.Providers,
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedSettings),
+ ConfigDirectory: acctest.NamedTestCaseDirectory("create_with_logging"),
+ ConfigVariables: config.Variables{
+ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
+ },
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_level", "debug"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_to_files", "true"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.go_max_procs", "2"),
+ ),
+ },
+ // Step 2: Update settings
+ {
+ ProtoV6ProviderFactories: acctest.Providers,
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedSettings),
+ ConfigDirectory: acctest.NamedTestCaseDirectory("update_settings"),
+ ConfigVariables: config.Variables{
+ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
+ },
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_level", "info"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_to_files", "true"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_files_keepfiles", "7"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_files_rotateeverybytes", "10485760"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.go_max_procs", "4"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.download_target_directory", "/tmp/elastic-agent"),
+ ),
+ },
+ // Step 3: Import state verification
+ {
+ ProtoV6ProviderFactories: acctest.Providers,
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedSettings),
+ ConfigDirectory: acctest.NamedTestCaseDirectory("update_settings"),
+ ConfigVariables: config.Variables{
+ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
+ },
+ ResourceName: "elasticstack_fleet_agent_policy.test_policy",
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateVerifyIgnore: []string{"skip_destroy"},
+ },
+ // Step 4: Remove settings
+ {
+ ProtoV6ProviderFactories: acctest.Providers,
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedSettings),
+ ConfigDirectory: acctest.NamedTestCaseDirectory("remove_settings"),
+ ConfigVariables: config.Variables{
+ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
+ },
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
+ ),
+ },
+ // Step 5: Set empty block - advanced_settings = {} applies schema defaults
+ {
+ ProtoV6ProviderFactories: acctest.Providers,
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedSettings),
+ ConfigDirectory: acctest.NamedTestCaseDirectory("set_to_defaults"),
+ ConfigVariables: config.Variables{
+ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
+ },
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "Test Agent Policy with Default Advanced Settings"),
+ // Empty block applies schema defaults for flat attributes
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_level", "info"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_to_files", "true"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_files_interval", "30s"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_files_keepfiles", "7"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_files_rotateeverybytes", "10485760"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.logging_metrics_period", "30s"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.go_max_procs", "0"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.download_timeout", "2h"),
+ // monitoring_runtime_experimental is not checked - it's null when not set (no default, UseStateForUnknown)
+ ),
+ },
+ },
+ })
+}
+
func TestAccResourceAgentPolicyWithAdvancedMonitoring(t *testing.T) {
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
@@ -731,6 +827,48 @@ func TestAccResourceAgentPolicyWithAdvancedMonitoring(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"skip_destroy"},
},
+ {
+ // Step 4: Remove advanced_monitoring_options from config
+ // UseStateForUnknown should preserve existing state values
+ ProtoV6ProviderFactories: acctest.Providers,
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedMonitoring),
+ ConfigDirectory: acctest.NamedTestCaseDirectory("remove_advanced_monitoring"),
+ ConfigVariables: config.Variables{
+ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
+ "skip_destroy": config.BoolVariable(false),
+ },
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "Test Agent Policy - No Advanced Monitoring"),
+ ),
+ },
+ {
+ // Step 5: Set empty nested blocks - schema defaults are applied
+ ProtoV6ProviderFactories: acctest.Providers,
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedMonitoring),
+ ConfigDirectory: acctest.NamedTestCaseDirectory("set_to_defaults"),
+ ConfigVariables: config.Variables{
+ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
+ "skip_destroy": config.BoolVariable(false),
+ },
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "Test Agent Policy with Default Advanced Monitoring"),
+ // Empty nested blocks apply schema defaults for leaf attributes
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.http_monitoring_endpoint.enabled", "false"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.http_monitoring_endpoint.host", "localhost"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.http_monitoring_endpoint.port", "6791"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.http_monitoring_endpoint.buffer_enabled", "false"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.http_monitoring_endpoint.pprof_enabled", "false"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.diagnostics.rate_limits.interval", "1m"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.diagnostics.rate_limits.burst", "1"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.diagnostics.file_uploader.init_duration", "1s"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.diagnostics.file_uploader.backoff_duration", "1m"),
+ resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_monitoring_options.diagnostics.file_uploader.max_retries", "10"),
+ ),
+ },
},
})
}
diff --git a/internal/fleet/agent_policy/models.go b/internal/fleet/agent_policy/models.go
index 3798804dd..4248369a8 100644
--- a/internal/fleet/agent_policy/models.go
+++ b/internal/fleet/agent_policy/models.go
@@ -14,9 +14,7 @@ import (
"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/types"
- "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
const (
@@ -43,6 +41,7 @@ type features struct {
SupportsRequiredVersions bool
SupportsAgentFeatures bool
SupportsAdvancedMonitoring bool
+ SupportsAdvancedSettings bool
}
type globalDataTagsItemModel struct {
@@ -50,50 +49,6 @@ type globalDataTagsItemModel struct {
NumberValue types.Float32 `tfsdk:"number_value"`
}
-// Advanced Monitoring Options models
-type advancedMonitoringOptionsModel struct {
- HttpMonitoringEndpoint types.Object `tfsdk:"http_monitoring_endpoint"`
- Diagnostics types.Object `tfsdk:"diagnostics"`
-}
-
-type httpMonitoringEndpointModel struct {
- Enabled types.Bool `tfsdk:"enabled"`
- Host types.String `tfsdk:"host"`
- Port types.Int32 `tfsdk:"port"`
- BufferEnabled types.Bool `tfsdk:"buffer_enabled"`
- PprofEnabled types.Bool `tfsdk:"pprof_enabled"`
-}
-
-type diagnosticsModel struct {
- RateLimits types.Object `tfsdk:"rate_limits"`
- FileUploader types.Object `tfsdk:"file_uploader"`
-}
-
-type rateLimitsModel struct {
- Interval customtypes.Duration `tfsdk:"interval"`
- Burst types.Int32 `tfsdk:"burst"`
-}
-
-type fileUploaderModel struct {
- InitDuration customtypes.Duration `tfsdk:"init_duration"`
- BackoffDuration customtypes.Duration `tfsdk:"backoff_duration"`
- MaxRetries types.Int32 `tfsdk:"max_retries"`
-}
-
-// Default values for advanced monitoring options
-const (
- defaultHttpMonitoringEnabled = false
- defaultHttpMonitoringHost = "localhost"
- defaultHttpMonitoringPort = 6791
- defaultHttpMonitoringBufferEnabled = false
- defaultHttpMonitoringPprofEnabled = false
- defaultDiagnosticsInterval = "1m"
- defaultDiagnosticsBurst = 1
- defaultDiagnosticsInitDuration = "1s"
- defaultDiagnosticsBackoffDuration = "1m"
- defaultDiagnosticsMaxRetries = 10
-)
-
type agentPolicyModel struct {
ID types.String `tfsdk:"id"`
PolicyID types.String `tfsdk:"policy_id"`
@@ -116,6 +71,7 @@ type agentPolicyModel struct {
SpaceIds types.Set `tfsdk:"space_ids"`
RequiredVersions types.Map `tfsdk:"required_versions"`
AdvancedMonitoringOptions types.Object `tfsdk:"advanced_monitoring_options"`
+ AdvancedSettings types.Object `tfsdk:"advanced_settings"`
}
func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.AgentPolicy) diag.Diagnostics {
@@ -206,7 +162,7 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.
}
- if data.SpaceIds != nil {
+ if data.SpaceIds != nil && len(*data.SpaceIds) > 0 {
spaceIds, d := types.SetValueFrom(ctx, types.StringType, *data.SpaceIds)
if d.HasError() {
return d
@@ -235,168 +191,17 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.
model.RequiredVersions = types.MapNull(types.Int32Type)
}
- // Handle advanced monitoring options
- if diags := model.populateAdvancedMonitoringFromAPI(ctx, data); diags.HasError() {
+ // Handle advanced_settings
+ if diags := model.populateAdvancedSettingsFromAPI(ctx, data); diags.HasError() {
return diags
}
- return nil
-}
-
-// populateAdvancedMonitoringFromAPI populates the advanced monitoring options from API response
-func (model *agentPolicyModel) populateAdvancedMonitoringFromAPI(ctx context.Context, data *kbapi.AgentPolicy) diag.Diagnostics {
- // Check if any advanced monitoring data exists in the API response
- hasHttpMonitoring := data.MonitoringHttp != nil
- hasPprofEnabled := data.MonitoringPprofEnabled != nil
- hasDiagnostics := data.MonitoringDiagnostics != nil
-
- if !hasHttpMonitoring && !hasPprofEnabled && !hasDiagnostics {
- // No advanced monitoring options in API response
- model.AdvancedMonitoringOptions = types.ObjectNull(advancedMonitoringOptionsAttrTypes())
- return nil
- }
-
- var httpEndpointObj types.Object
- var diagnosticsObj types.Object
-
- // Populate HTTP monitoring endpoint
- if hasHttpMonitoring || hasPprofEnabled {
- httpEndpoint := httpMonitoringEndpointModel{
- Enabled: types.BoolValue(defaultHttpMonitoringEnabled),
- Host: types.StringValue(defaultHttpMonitoringHost),
- Port: types.Int32Value(defaultHttpMonitoringPort),
- BufferEnabled: types.BoolValue(defaultHttpMonitoringBufferEnabled),
- PprofEnabled: types.BoolValue(defaultHttpMonitoringPprofEnabled),
- }
-
- if data.MonitoringHttp != nil {
- if data.MonitoringHttp.Enabled != nil {
- httpEndpoint.Enabled = types.BoolValue(*data.MonitoringHttp.Enabled)
- }
- if data.MonitoringHttp.Host != nil {
- httpEndpoint.Host = types.StringValue(*data.MonitoringHttp.Host)
- }
- if data.MonitoringHttp.Port != nil {
- httpEndpoint.Port = types.Int32Value(int32(*data.MonitoringHttp.Port))
- }
- if data.MonitoringHttp.Buffer != nil && data.MonitoringHttp.Buffer.Enabled != nil {
- httpEndpoint.BufferEnabled = types.BoolValue(*data.MonitoringHttp.Buffer.Enabled)
- }
- }
-
- if data.MonitoringPprofEnabled != nil {
- httpEndpoint.PprofEnabled = types.BoolValue(*data.MonitoringPprofEnabled)
- }
-
- obj, diags := types.ObjectValueFrom(ctx, httpMonitoringEndpointAttrTypes(), httpEndpoint)
- if diags.HasError() {
- return diags
- }
- httpEndpointObj = obj
- } else {
- httpEndpointObj = types.ObjectNull(httpMonitoringEndpointAttrTypes())
- }
-
- // Populate diagnostics
- if hasDiagnostics {
- var rateLimitsObj types.Object
- var fileUploaderObj types.Object
-
- if data.MonitoringDiagnostics.Limit != nil {
- rateLimits := rateLimitsModel{
- Interval: customtypes.NewDurationValue(defaultDiagnosticsInterval),
- Burst: types.Int32Value(defaultDiagnosticsBurst),
- }
- if data.MonitoringDiagnostics.Limit.Interval != nil {
- rateLimits.Interval = customtypes.NewDurationValue(*data.MonitoringDiagnostics.Limit.Interval)
- }
- if data.MonitoringDiagnostics.Limit.Burst != nil {
- rateLimits.Burst = types.Int32Value(int32(*data.MonitoringDiagnostics.Limit.Burst))
- }
- obj, diags := types.ObjectValueFrom(ctx, rateLimitsAttrTypes(), rateLimits)
- if diags.HasError() {
- return diags
- }
- rateLimitsObj = obj
- } else {
- rateLimitsObj = types.ObjectNull(rateLimitsAttrTypes())
- }
-
- if data.MonitoringDiagnostics.Uploader != nil {
- fileUploader := fileUploaderModel{
- InitDuration: customtypes.NewDurationValue(defaultDiagnosticsInitDuration),
- BackoffDuration: customtypes.NewDurationValue(defaultDiagnosticsBackoffDuration),
- MaxRetries: types.Int32Value(defaultDiagnosticsMaxRetries),
- }
- if data.MonitoringDiagnostics.Uploader.InitDur != nil {
- fileUploader.InitDuration = customtypes.NewDurationValue(*data.MonitoringDiagnostics.Uploader.InitDur)
- }
- if data.MonitoringDiagnostics.Uploader.MaxDur != nil {
- fileUploader.BackoffDuration = customtypes.NewDurationValue(*data.MonitoringDiagnostics.Uploader.MaxDur)
- }
- if data.MonitoringDiagnostics.Uploader.MaxRetries != nil {
- fileUploader.MaxRetries = types.Int32Value(int32(*data.MonitoringDiagnostics.Uploader.MaxRetries))
- }
- obj, diags := types.ObjectValueFrom(ctx, fileUploaderAttrTypes(), fileUploader)
- if diags.HasError() {
- return diags
- }
- fileUploaderObj = obj
- } else {
- fileUploaderObj = types.ObjectNull(fileUploaderAttrTypes())
- }
-
- diagModel := diagnosticsModel{
- RateLimits: rateLimitsObj,
- FileUploader: fileUploaderObj,
- }
- obj, diags := types.ObjectValueFrom(ctx, diagnosticsAttrTypes(), diagModel)
- if diags.HasError() {
- return diags
- }
- diagnosticsObj = obj
- } else {
- diagnosticsObj = types.ObjectNull(diagnosticsAttrTypes())
- }
-
- amo := advancedMonitoringOptionsModel{
- HttpMonitoringEndpoint: httpEndpointObj,
- Diagnostics: diagnosticsObj,
- }
-
- obj, diags := types.ObjectValueFrom(ctx, advancedMonitoringOptionsAttrTypes(), amo)
- if diags.HasError() {
+ // Handle advanced monitoring options
+ if diags := model.populateAdvancedMonitoringFromAPI(ctx, data); diags.HasError() {
return diags
}
- model.AdvancedMonitoringOptions = obj
- return nil
-}
-
-// Attribute type helpers for advanced monitoring options - pulled from schema to avoid duplication
-func advancedMonitoringOptionsAttrTypes() map[string]attr.Type {
- return getSchema().Attributes["advanced_monitoring_options"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
-}
-func httpMonitoringEndpointAttrTypes() map[string]attr.Type {
- amoAttr := getSchema().Attributes["advanced_monitoring_options"].(schema.SingleNestedAttribute)
- return amoAttr.Attributes["http_monitoring_endpoint"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
-}
-
-func diagnosticsAttrTypes() map[string]attr.Type {
- amoAttr := getSchema().Attributes["advanced_monitoring_options"].(schema.SingleNestedAttribute)
- return amoAttr.Attributes["diagnostics"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
-}
-
-func rateLimitsAttrTypes() map[string]attr.Type {
- amoAttr := getSchema().Attributes["advanced_monitoring_options"].(schema.SingleNestedAttribute)
- diagAttr := amoAttr.Attributes["diagnostics"].(schema.SingleNestedAttribute)
- return diagAttr.Attributes["rate_limits"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
-}
-
-func fileUploaderAttrTypes() map[string]attr.Type {
- amoAttr := getSchema().Attributes["advanced_monitoring_options"].(schema.SingleNestedAttribute)
- diagAttr := amoAttr.Attributes["diagnostics"].(schema.SingleNestedAttribute)
- return diagAttr.Attributes["file_uploader"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
+ return nil
}
// convertGlobalDataTags converts the global data tags from terraform model to API model
@@ -637,6 +442,20 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat featur
}
}
+ // Handle advanced_settings
+ if utils.IsKnown(model.AdvancedSettings) {
+ if !feat.SupportsAdvancedSettings {
+ return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diag.Diagnostics{
+ diag.NewAttributeErrorDiagnostic(
+ path.Root("advanced_settings"),
+ "Unsupported Elasticsearch version",
+ fmt.Sprintf("Advanced settings are only supported in Elastic Stack %s and above", MinVersionAdvancedSettings),
+ ),
+ }
+ }
+ body.AdvancedSettings = model.convertAdvancedSettingsToAPI(ctx)
+ }
+
// Handle advanced monitoring options
if utils.IsKnown(model.AdvancedMonitoringOptions) {
if !feat.SupportsAdvancedMonitoring {
@@ -782,6 +601,20 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat featur
body.AgentFeatures = &existingFeatures
}
+ // Handle advanced_settings
+ if utils.IsKnown(model.AdvancedSettings) {
+ if !feat.SupportsAdvancedSettings {
+ return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diag.Diagnostics{
+ diag.NewAttributeErrorDiagnostic(
+ path.Root("advanced_settings"),
+ "Unsupported Elasticsearch version",
+ fmt.Sprintf("Advanced settings are only supported in Elastic Stack %s and above", MinVersionAdvancedSettings),
+ ),
+ }
+ }
+ body.AdvancedSettings = model.convertAdvancedSettingsToAPI(ctx)
+ }
+
// Handle advanced monitoring options
if utils.IsKnown(model.AdvancedMonitoringOptions) {
if !feat.SupportsAdvancedMonitoring {
@@ -850,153 +683,3 @@ func mergeAgentFeature(existing []apiAgentFeature, newFeature *apiAgentFeature)
return &result
}
-
-// httpMonitoringEndpointAPIResult is the return type for convertHttpMonitoringEndpointToAPI
-// This type alias matches the inline struct expected by kbapi.PostFleetAgentPoliciesJSONRequestBody.MonitoringHttp
-type httpMonitoringEndpointAPIResult = struct {
- Buffer *struct {
- Enabled *bool `json:"enabled,omitempty"`
- } `json:"buffer,omitempty"`
- Enabled *bool `json:"enabled,omitempty"`
- Host *string `json:"host,omitempty"`
- Port *float32 `json:"port,omitempty"`
-}
-
-// convertHttpMonitoringEndpointToAPI converts the HTTP monitoring endpoint config to API format
-func (model *agentPolicyModel) convertHttpMonitoringEndpointToAPI(ctx context.Context) (*httpMonitoringEndpointAPIResult, *bool) {
- if !utils.IsKnown(model.AdvancedMonitoringOptions) {
- return nil, nil
- }
-
- var amo advancedMonitoringOptionsModel
- model.AdvancedMonitoringOptions.As(ctx, &amo, basetypes.ObjectAsOptions{})
-
- if !utils.IsKnown(amo.HttpMonitoringEndpoint) {
- return nil, nil
- }
-
- var http httpMonitoringEndpointModel
- amo.HttpMonitoringEndpoint.As(ctx, &http, basetypes.ObjectAsOptions{})
-
- // Check if any values differ from defaults
- hasNonDefaultValues := http.Enabled.ValueBool() ||
- http.Host.ValueString() != defaultHttpMonitoringHost ||
- http.Port.ValueInt32() != defaultHttpMonitoringPort ||
- http.BufferEnabled.ValueBool() ||
- http.PprofEnabled.ValueBool()
-
- if !hasNonDefaultValues {
- return nil, nil
- }
-
- enabled := http.Enabled.ValueBool()
- host := http.Host.ValueString()
- port := float32(http.Port.ValueInt32())
- bufferEnabled := http.BufferEnabled.ValueBool()
- pprofEnabled := http.PprofEnabled.ValueBool()
-
- result := &httpMonitoringEndpointAPIResult{
- Enabled: &enabled,
- Host: &host,
- Port: &port,
- Buffer: &struct {
- Enabled *bool `json:"enabled,omitempty"`
- }{
- Enabled: &bufferEnabled,
- },
- }
-
- return result, &pprofEnabled
-}
-
-// diagnosticsAPIResult is the return type for convertDiagnosticsToAPI
-// This type alias matches the inline struct expected by kbapi.PostFleetAgentPoliciesJSONRequestBody.MonitoringDiagnostics
-type diagnosticsAPIResult = struct {
- Limit *struct {
- Burst *float32 `json:"burst,omitempty"`
- Interval *string `json:"interval,omitempty"`
- } `json:"limit,omitempty"`
- Uploader *struct {
- InitDur *string `json:"init_dur,omitempty"`
- MaxDur *string `json:"max_dur,omitempty"`
- MaxRetries *float32 `json:"max_retries,omitempty"`
- } `json:"uploader,omitempty"`
-}
-
-// convertDiagnosticsToAPI converts the diagnostics config to API format
-func (model *agentPolicyModel) convertDiagnosticsToAPI(ctx context.Context) *diagnosticsAPIResult {
- if !utils.IsKnown(model.AdvancedMonitoringOptions) {
- return nil
- }
-
- var amo advancedMonitoringOptionsModel
- model.AdvancedMonitoringOptions.As(ctx, &amo, basetypes.ObjectAsOptions{})
-
- if !utils.IsKnown(amo.Diagnostics) {
- return nil
- }
-
- var diag diagnosticsModel
- amo.Diagnostics.As(ctx, &diag, basetypes.ObjectAsOptions{})
-
- // Check if any values differ from defaults
- hasNonDefaultValues := false
-
- if utils.IsKnown(diag.RateLimits) {
- var rateLimits rateLimitsModel
- diag.RateLimits.As(ctx, &rateLimits, basetypes.ObjectAsOptions{})
- if rateLimits.Interval.ValueString() != defaultDiagnosticsInterval ||
- rateLimits.Burst.ValueInt32() != defaultDiagnosticsBurst {
- hasNonDefaultValues = true
- }
- }
-
- if utils.IsKnown(diag.FileUploader) {
- var fileUploader fileUploaderModel
- diag.FileUploader.As(ctx, &fileUploader, basetypes.ObjectAsOptions{})
- if fileUploader.InitDuration.ValueString() != defaultDiagnosticsInitDuration ||
- fileUploader.BackoffDuration.ValueString() != defaultDiagnosticsBackoffDuration ||
- fileUploader.MaxRetries.ValueInt32() != defaultDiagnosticsMaxRetries {
- hasNonDefaultValues = true
- }
- }
-
- if !hasNonDefaultValues {
- return nil
- }
-
- result := &diagnosticsAPIResult{}
-
- if utils.IsKnown(diag.RateLimits) {
- var rateLimits rateLimitsModel
- diag.RateLimits.As(ctx, &rateLimits, basetypes.ObjectAsOptions{})
- interval := rateLimits.Interval.ValueString()
- burst := float32(rateLimits.Burst.ValueInt32())
- result.Limit = &struct {
- Burst *float32 `json:"burst,omitempty"`
- Interval *string `json:"interval,omitempty"`
- }{
- Interval: &interval,
- Burst: &burst,
- }
- }
-
- if utils.IsKnown(diag.FileUploader) {
- var fileUploader fileUploaderModel
- diag.FileUploader.As(ctx, &fileUploader, basetypes.ObjectAsOptions{})
- initDur := fileUploader.InitDuration.ValueString()
- maxDur := fileUploader.BackoffDuration.ValueString()
- maxRetries := float32(fileUploader.MaxRetries.ValueInt32())
- result.Uploader = &struct {
- InitDur *string `json:"init_dur,omitempty"`
- MaxDur *string `json:"max_dur,omitempty"`
- MaxRetries *float32 `json:"max_retries,omitempty"`
- }{
- InitDur: &initDur,
- MaxDur: &maxDur,
- MaxRetries: &maxRetries,
- }
- }
-
- return result
-}
diff --git a/internal/fleet/agent_policy/models_advanced_monitoring.go b/internal/fleet/agent_policy/models_advanced_monitoring.go
new file mode 100644
index 000000000..6f41137e6
--- /dev/null
+++ b/internal/fleet/agent_policy/models_advanced_monitoring.go
@@ -0,0 +1,328 @@
+package agent_policy
+
+import (
+ "context"
+
+ "github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils"
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+)
+
+// Advanced Monitoring Options models
+type advancedMonitoringOptionsModel struct {
+ HttpMonitoringEndpoint types.Object `tfsdk:"http_monitoring_endpoint"`
+ Diagnostics types.Object `tfsdk:"diagnostics"`
+}
+
+type httpMonitoringEndpointModel struct {
+ Enabled types.Bool `tfsdk:"enabled"`
+ Host types.String `tfsdk:"host"`
+ Port types.Int32 `tfsdk:"port"`
+ BufferEnabled types.Bool `tfsdk:"buffer_enabled"`
+ PprofEnabled types.Bool `tfsdk:"pprof_enabled"`
+}
+
+type diagnosticsModel struct {
+ RateLimits types.Object `tfsdk:"rate_limits"`
+ FileUploader types.Object `tfsdk:"file_uploader"`
+}
+
+type rateLimitsModel struct {
+ Interval customtypes.Duration `tfsdk:"interval"`
+ Burst types.Int32 `tfsdk:"burst"`
+}
+
+type fileUploaderModel struct {
+ InitDuration customtypes.Duration `tfsdk:"init_duration"`
+ BackoffDuration customtypes.Duration `tfsdk:"backoff_duration"`
+ MaxRetries types.Int32 `tfsdk:"max_retries"`
+}
+
+// Default values for advanced monitoring options
+const (
+ defaultHttpMonitoringEnabled = false
+ defaultHttpMonitoringHost = "localhost"
+ defaultHttpMonitoringPort = 6791
+ defaultHttpMonitoringBufferEnabled = false
+ defaultHttpMonitoringPprofEnabled = false
+ defaultDiagnosticsInterval = "1m"
+ defaultDiagnosticsBurst = 1
+ defaultDiagnosticsInitDuration = "1s"
+ defaultDiagnosticsBackoffDuration = "1m"
+ defaultDiagnosticsMaxRetries = 10
+)
+
+// Attribute type helpers for advanced monitoring options - pulled from schema to avoid duplication
+func advancedMonitoringOptionsAttrTypes() map[string]attr.Type {
+ return getSchema().Attributes["advanced_monitoring_options"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
+}
+
+func httpMonitoringEndpointAttrTypes() map[string]attr.Type {
+ amoAttr := getSchema().Attributes["advanced_monitoring_options"].(schema.SingleNestedAttribute)
+ return amoAttr.Attributes["http_monitoring_endpoint"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
+}
+
+func diagnosticsAttrTypes() map[string]attr.Type {
+ amoAttr := getSchema().Attributes["advanced_monitoring_options"].(schema.SingleNestedAttribute)
+ return amoAttr.Attributes["diagnostics"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
+}
+
+func rateLimitsAttrTypes() map[string]attr.Type {
+ amoAttr := getSchema().Attributes["advanced_monitoring_options"].(schema.SingleNestedAttribute)
+ diagAttr := amoAttr.Attributes["diagnostics"].(schema.SingleNestedAttribute)
+ return diagAttr.Attributes["rate_limits"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
+}
+
+func fileUploaderAttrTypes() map[string]attr.Type {
+ amoAttr := getSchema().Attributes["advanced_monitoring_options"].(schema.SingleNestedAttribute)
+ diagAttr := amoAttr.Attributes["diagnostics"].(schema.SingleNestedAttribute)
+ return diagAttr.Attributes["file_uploader"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
+}
+
+// populateAdvancedMonitoringFromAPI populates the advanced monitoring options from API response
+func (model *agentPolicyModel) populateAdvancedMonitoringFromAPI(ctx context.Context, data *kbapi.AgentPolicy) diag.Diagnostics {
+ // Check if any advanced monitoring data exists in the API response
+ hasHttpMonitoring := data.MonitoringHttp != nil
+ hasPprofEnabled := data.MonitoringPprofEnabled != nil
+ hasDiagnostics := data.MonitoringDiagnostics != nil
+
+ if !hasHttpMonitoring && !hasPprofEnabled && !hasDiagnostics {
+ // No advanced monitoring options in API response
+ model.AdvancedMonitoringOptions = types.ObjectNull(advancedMonitoringOptionsAttrTypes())
+ return nil
+ }
+
+ var httpEndpointObj types.Object
+ var diagnosticsObj types.Object
+
+ // Populate HTTP monitoring endpoint
+ if hasHttpMonitoring || hasPprofEnabled {
+ httpEndpoint := httpMonitoringEndpointModel{
+ Enabled: types.BoolValue(defaultHttpMonitoringEnabled),
+ Host: types.StringValue(defaultHttpMonitoringHost),
+ Port: types.Int32Value(defaultHttpMonitoringPort),
+ BufferEnabled: types.BoolValue(defaultHttpMonitoringBufferEnabled),
+ PprofEnabled: types.BoolValue(defaultHttpMonitoringPprofEnabled),
+ }
+
+ if data.MonitoringHttp != nil {
+ if data.MonitoringHttp.Enabled != nil {
+ httpEndpoint.Enabled = types.BoolValue(*data.MonitoringHttp.Enabled)
+ }
+ if data.MonitoringHttp.Host != nil {
+ httpEndpoint.Host = types.StringValue(*data.MonitoringHttp.Host)
+ }
+ if data.MonitoringHttp.Port != nil {
+ httpEndpoint.Port = types.Int32Value(int32(*data.MonitoringHttp.Port))
+ }
+ if data.MonitoringHttp.Buffer != nil && data.MonitoringHttp.Buffer.Enabled != nil {
+ httpEndpoint.BufferEnabled = types.BoolValue(*data.MonitoringHttp.Buffer.Enabled)
+ }
+ }
+
+ if data.MonitoringPprofEnabled != nil {
+ httpEndpoint.PprofEnabled = types.BoolValue(*data.MonitoringPprofEnabled)
+ }
+
+ obj, diags := types.ObjectValueFrom(ctx, httpMonitoringEndpointAttrTypes(), httpEndpoint)
+ if diags.HasError() {
+ return diags
+ }
+ httpEndpointObj = obj
+ } else {
+ httpEndpointObj = types.ObjectNull(httpMonitoringEndpointAttrTypes())
+ }
+
+ // Populate diagnostics
+ if hasDiagnostics {
+ var rateLimitsObj types.Object
+ var fileUploaderObj types.Object
+
+ if data.MonitoringDiagnostics.Limit != nil {
+ rateLimits := rateLimitsModel{
+ Interval: customtypes.NewDurationValue(defaultDiagnosticsInterval),
+ Burst: types.Int32Value(defaultDiagnosticsBurst),
+ }
+ if data.MonitoringDiagnostics.Limit.Interval != nil {
+ rateLimits.Interval = customtypes.NewDurationValue(*data.MonitoringDiagnostics.Limit.Interval)
+ }
+ if data.MonitoringDiagnostics.Limit.Burst != nil {
+ rateLimits.Burst = types.Int32Value(int32(*data.MonitoringDiagnostics.Limit.Burst))
+ }
+ obj, diags := types.ObjectValueFrom(ctx, rateLimitsAttrTypes(), rateLimits)
+ if diags.HasError() {
+ return diags
+ }
+ rateLimitsObj = obj
+ } else {
+ rateLimitsObj = types.ObjectNull(rateLimitsAttrTypes())
+ }
+
+ if data.MonitoringDiagnostics.Uploader != nil {
+ fileUploader := fileUploaderModel{
+ InitDuration: customtypes.NewDurationValue(defaultDiagnosticsInitDuration),
+ BackoffDuration: customtypes.NewDurationValue(defaultDiagnosticsBackoffDuration),
+ MaxRetries: types.Int32Value(defaultDiagnosticsMaxRetries),
+ }
+ if data.MonitoringDiagnostics.Uploader.InitDur != nil {
+ fileUploader.InitDuration = customtypes.NewDurationValue(*data.MonitoringDiagnostics.Uploader.InitDur)
+ }
+ if data.MonitoringDiagnostics.Uploader.MaxDur != nil {
+ fileUploader.BackoffDuration = customtypes.NewDurationValue(*data.MonitoringDiagnostics.Uploader.MaxDur)
+ }
+ if data.MonitoringDiagnostics.Uploader.MaxRetries != nil {
+ fileUploader.MaxRetries = types.Int32Value(int32(*data.MonitoringDiagnostics.Uploader.MaxRetries))
+ }
+ obj, diags := types.ObjectValueFrom(ctx, fileUploaderAttrTypes(), fileUploader)
+ if diags.HasError() {
+ return diags
+ }
+ fileUploaderObj = obj
+ } else {
+ fileUploaderObj = types.ObjectNull(fileUploaderAttrTypes())
+ }
+
+ diagModel := diagnosticsModel{
+ RateLimits: rateLimitsObj,
+ FileUploader: fileUploaderObj,
+ }
+ obj, diags := types.ObjectValueFrom(ctx, diagnosticsAttrTypes(), diagModel)
+ if diags.HasError() {
+ return diags
+ }
+ diagnosticsObj = obj
+ } else {
+ diagnosticsObj = types.ObjectNull(diagnosticsAttrTypes())
+ }
+
+ amo := advancedMonitoringOptionsModel{
+ HttpMonitoringEndpoint: httpEndpointObj,
+ Diagnostics: diagnosticsObj,
+ }
+
+ obj, diags := types.ObjectValueFrom(ctx, advancedMonitoringOptionsAttrTypes(), amo)
+ if diags.HasError() {
+ return diags
+ }
+ model.AdvancedMonitoringOptions = obj
+ return nil
+}
+
+// httpMonitoringEndpointAPIResult is the return type for convertHttpMonitoringEndpointToAPI
+// This type alias matches the inline struct expected by kbapi.PostFleetAgentPoliciesJSONRequestBody.MonitoringHttp
+type httpMonitoringEndpointAPIResult = struct {
+ Buffer *struct {
+ Enabled *bool `json:"enabled,omitempty"`
+ } `json:"buffer,omitempty"`
+ Enabled *bool `json:"enabled,omitempty"`
+ Host *string `json:"host,omitempty"`
+ Port *float32 `json:"port,omitempty"`
+}
+
+// convertHttpMonitoringEndpointToAPI converts the HTTP monitoring endpoint config to API format
+func (model *agentPolicyModel) convertHttpMonitoringEndpointToAPI(ctx context.Context) (*httpMonitoringEndpointAPIResult, *bool) {
+ if !utils.IsKnown(model.AdvancedMonitoringOptions) {
+ return nil, nil
+ }
+
+ var amo advancedMonitoringOptionsModel
+ model.AdvancedMonitoringOptions.As(ctx, &amo, basetypes.ObjectAsOptions{})
+
+ if !utils.IsKnown(amo.HttpMonitoringEndpoint) {
+ return nil, nil
+ }
+
+ var http httpMonitoringEndpointModel
+ amo.HttpMonitoringEndpoint.As(ctx, &http, basetypes.ObjectAsOptions{})
+
+ enabled := http.Enabled.ValueBool()
+ host := http.Host.ValueString()
+ port := float32(http.Port.ValueInt32())
+ bufferEnabled := http.BufferEnabled.ValueBool()
+ pprofEnabled := http.PprofEnabled.ValueBool()
+
+ result := &httpMonitoringEndpointAPIResult{
+ Enabled: &enabled,
+ Host: &host,
+ Port: &port,
+ Buffer: &struct {
+ Enabled *bool `json:"enabled,omitempty"`
+ }{
+ Enabled: &bufferEnabled,
+ },
+ }
+
+ return result, &pprofEnabled
+}
+
+// diagnosticsAPIResult is the return type for convertDiagnosticsToAPI
+// This type alias matches the inline struct expected by kbapi.PostFleetAgentPoliciesJSONRequestBody.MonitoringDiagnostics
+type diagnosticsAPIResult = struct {
+ Limit *struct {
+ Burst *float32 `json:"burst,omitempty"`
+ Interval *string `json:"interval,omitempty"`
+ } `json:"limit,omitempty"`
+ Uploader *struct {
+ InitDur *string `json:"init_dur,omitempty"`
+ MaxDur *string `json:"max_dur,omitempty"`
+ MaxRetries *float32 `json:"max_retries,omitempty"`
+ } `json:"uploader,omitempty"`
+}
+
+// convertDiagnosticsToAPI converts the diagnostics config to API format
+func (model *agentPolicyModel) convertDiagnosticsToAPI(ctx context.Context) *diagnosticsAPIResult {
+ if !utils.IsKnown(model.AdvancedMonitoringOptions) {
+ return nil
+ }
+
+ var amo advancedMonitoringOptionsModel
+ model.AdvancedMonitoringOptions.As(ctx, &amo, basetypes.ObjectAsOptions{})
+
+ if !utils.IsKnown(amo.Diagnostics) {
+ return nil
+ }
+
+ var diag diagnosticsModel
+ amo.Diagnostics.As(ctx, &diag, basetypes.ObjectAsOptions{})
+
+ result := &diagnosticsAPIResult{}
+
+ if utils.IsKnown(diag.RateLimits) {
+ var rateLimits rateLimitsModel
+ diag.RateLimits.As(ctx, &rateLimits, basetypes.ObjectAsOptions{})
+ interval := rateLimits.Interval.ValueString()
+ burst := float32(rateLimits.Burst.ValueInt32())
+ result.Limit = &struct {
+ Burst *float32 `json:"burst,omitempty"`
+ Interval *string `json:"interval,omitempty"`
+ }{
+ Interval: &interval,
+ Burst: &burst,
+ }
+ }
+
+ if utils.IsKnown(diag.FileUploader) {
+ var fileUploader fileUploaderModel
+ diag.FileUploader.As(ctx, &fileUploader, basetypes.ObjectAsOptions{})
+ initDur := fileUploader.InitDuration.ValueString()
+ maxDur := fileUploader.BackoffDuration.ValueString()
+ maxRetries := float32(fileUploader.MaxRetries.ValueInt32())
+ result.Uploader = &struct {
+ InitDur *string `json:"init_dur,omitempty"`
+ MaxDur *string `json:"max_dur,omitempty"`
+ MaxRetries *float32 `json:"max_retries,omitempty"`
+ }{
+ InitDur: &initDur,
+ MaxDur: &maxDur,
+ MaxRetries: &maxRetries,
+ }
+ }
+
+ return result
+}
diff --git a/internal/fleet/agent_policy/models_advanced_monitoring_test.go b/internal/fleet/agent_policy/models_advanced_monitoring_test.go
new file mode 100644
index 000000000..ca6003321
--- /dev/null
+++ b/internal/fleet/agent_policy/models_advanced_monitoring_test.go
@@ -0,0 +1,263 @@
+package agent_policy
+
+import (
+ "context"
+ "testing"
+
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestConvertHttpMonitoringEndpointToAPI(t *testing.T) {
+ ctx := context.Background()
+
+ // Helper to create types.Object from httpMonitoringEndpointModel
+ createHttpEndpointObject := func(m httpMonitoringEndpointModel) types.Object {
+ obj, _ := types.ObjectValueFrom(ctx, httpMonitoringEndpointAttrTypes(), m)
+ return obj
+ }
+
+ // Helper to create types.Object from advancedMonitoringOptionsModel
+ createAmoObject := func(httpEndpoint types.Object) types.Object {
+ amo := advancedMonitoringOptionsModel{
+ HttpMonitoringEndpoint: httpEndpoint,
+ Diagnostics: types.ObjectNull(diagnosticsAttrTypes()),
+ }
+ obj, _ := types.ObjectValueFrom(ctx, advancedMonitoringOptionsAttrTypes(), amo)
+ return obj
+ }
+
+ tests := []struct {
+ name string
+ amo types.Object
+ wantHttp bool
+ wantPprof bool
+ wantPprofValue bool
+ }{
+ {
+ name: "null advanced monitoring options returns nil",
+ amo: types.ObjectNull(advancedMonitoringOptionsAttrTypes()),
+ wantHttp: false,
+ },
+ {
+ name: "null http monitoring endpoint returns nil",
+ amo: createAmoObject(types.ObjectNull(httpMonitoringEndpointAttrTypes())),
+ wantHttp: false,
+ },
+ {
+ name: "default values are sent (allows reset to defaults)",
+ amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{
+ Enabled: types.BoolValue(false),
+ Host: types.StringValue("localhost"),
+ Port: types.Int32Value(6791),
+ BufferEnabled: types.BoolValue(false),
+ PprofEnabled: types.BoolValue(false),
+ })),
+ wantHttp: true,
+ wantPprof: true,
+ wantPprofValue: false,
+ },
+ {
+ name: "enabled http endpoint returns values",
+ amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{
+ Enabled: types.BoolValue(true),
+ Host: types.StringValue("localhost"),
+ Port: types.Int32Value(6791),
+ BufferEnabled: types.BoolValue(false),
+ PprofEnabled: types.BoolValue(false),
+ })),
+ wantHttp: true,
+ wantPprof: true,
+ wantPprofValue: false,
+ },
+ {
+ name: "custom port returns values",
+ amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{
+ Enabled: types.BoolValue(false),
+ Host: types.StringValue("localhost"),
+ Port: types.Int32Value(8080),
+ BufferEnabled: types.BoolValue(false),
+ PprofEnabled: types.BoolValue(false),
+ })),
+ wantHttp: true,
+ wantPprof: true,
+ wantPprofValue: false,
+ },
+ {
+ name: "pprof enabled returns values",
+ amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{
+ Enabled: types.BoolValue(true),
+ Host: types.StringValue("localhost"),
+ Port: types.Int32Value(6791),
+ BufferEnabled: types.BoolValue(false),
+ PprofEnabled: types.BoolValue(true),
+ })),
+ wantHttp: true,
+ wantPprof: true,
+ wantPprofValue: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ model := &agentPolicyModel{
+ AdvancedMonitoringOptions: tt.amo,
+ }
+
+ gotHttp, gotPprof := model.convertHttpMonitoringEndpointToAPI(ctx)
+
+ if !tt.wantHttp {
+ assert.Nil(t, gotHttp)
+ assert.Nil(t, gotPprof)
+ return
+ }
+
+ assert.NotNil(t, gotHttp)
+ if tt.wantPprof {
+ assert.NotNil(t, gotPprof)
+ assert.Equal(t, tt.wantPprofValue, *gotPprof)
+ }
+ })
+ }
+}
+
+func TestConvertDiagnosticsToAPI(t *testing.T) {
+ ctx := context.Background()
+
+ // Helper to create types.Object from rateLimitsModel
+ createRateLimitsObject := func(m rateLimitsModel) types.Object {
+ obj, _ := types.ObjectValueFrom(ctx, rateLimitsAttrTypes(), m)
+ return obj
+ }
+
+ // Helper to create types.Object from fileUploaderModel
+ createFileUploaderObject := func(m fileUploaderModel) types.Object {
+ obj, _ := types.ObjectValueFrom(ctx, fileUploaderAttrTypes(), m)
+ return obj
+ }
+
+ // Helper to create types.Object from diagnosticsModel
+ createDiagnosticsObject := func(rateLimits, fileUploader types.Object) types.Object {
+ diag := diagnosticsModel{
+ RateLimits: rateLimits,
+ FileUploader: fileUploader,
+ }
+ obj, _ := types.ObjectValueFrom(ctx, diagnosticsAttrTypes(), diag)
+ return obj
+ }
+
+ // Helper to create types.Object from advancedMonitoringOptionsModel
+ createAmoObject := func(diagnostics types.Object) types.Object {
+ amo := advancedMonitoringOptionsModel{
+ HttpMonitoringEndpoint: types.ObjectNull(httpMonitoringEndpointAttrTypes()),
+ Diagnostics: diagnostics,
+ }
+ obj, _ := types.ObjectValueFrom(ctx, advancedMonitoringOptionsAttrTypes(), amo)
+ return obj
+ }
+
+ tests := []struct {
+ name string
+ amo types.Object
+ wantDiag bool
+ wantRateLimits bool
+ wantUploader bool
+ }{
+ {
+ name: "null advanced monitoring options returns nil",
+ amo: types.ObjectNull(advancedMonitoringOptionsAttrTypes()),
+ wantDiag: false,
+ },
+ {
+ name: "null diagnostics returns nil",
+ amo: createAmoObject(types.ObjectNull(diagnosticsAttrTypes())),
+ wantDiag: false,
+ },
+ {
+ name: "default rate limits values are sent (allows reset to defaults)",
+ amo: createAmoObject(createDiagnosticsObject(
+ createRateLimitsObject(rateLimitsModel{
+ Interval: customtypes.NewDurationValue("1m"),
+ Burst: types.Int32Value(1),
+ }),
+ types.ObjectNull(fileUploaderAttrTypes()),
+ )),
+ wantDiag: true,
+ wantRateLimits: true,
+ },
+ {
+ name: "default uploader values are sent (allows reset to defaults)",
+ amo: createAmoObject(createDiagnosticsObject(
+ types.ObjectNull(rateLimitsAttrTypes()),
+ createFileUploaderObject(fileUploaderModel{
+ InitDuration: customtypes.NewDurationValue("1s"),
+ BackoffDuration: customtypes.NewDurationValue("1m"),
+ MaxRetries: types.Int32Value(10),
+ }),
+ )),
+ wantDiag: true,
+ wantUploader: true,
+ },
+ {
+ name: "custom rate limits interval returns values",
+ amo: createAmoObject(createDiagnosticsObject(
+ createRateLimitsObject(rateLimitsModel{
+ Interval: customtypes.NewDurationValue("2m"),
+ Burst: types.Int32Value(1),
+ }),
+ types.ObjectNull(fileUploaderAttrTypes()),
+ )),
+ wantDiag: true,
+ wantRateLimits: true,
+ },
+ {
+ name: "custom rate limits burst returns values",
+ amo: createAmoObject(createDiagnosticsObject(
+ createRateLimitsObject(rateLimitsModel{
+ Interval: customtypes.NewDurationValue("1m"),
+ Burst: types.Int32Value(5),
+ }),
+ types.ObjectNull(fileUploaderAttrTypes()),
+ )),
+ wantDiag: true,
+ wantRateLimits: true,
+ },
+ {
+ name: "custom uploader max_retries returns values",
+ amo: createAmoObject(createDiagnosticsObject(
+ types.ObjectNull(rateLimitsAttrTypes()),
+ createFileUploaderObject(fileUploaderModel{
+ InitDuration: customtypes.NewDurationValue("1s"),
+ BackoffDuration: customtypes.NewDurationValue("1m"),
+ MaxRetries: types.Int32Value(20),
+ }),
+ )),
+ wantDiag: true,
+ wantUploader: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ model := &agentPolicyModel{
+ AdvancedMonitoringOptions: tt.amo,
+ }
+
+ got := model.convertDiagnosticsToAPI(ctx)
+
+ if !tt.wantDiag {
+ assert.Nil(t, got)
+ return
+ }
+
+ assert.NotNil(t, got)
+ if tt.wantRateLimits {
+ assert.NotNil(t, got.Limit)
+ }
+ if tt.wantUploader {
+ assert.NotNil(t, got.Uploader)
+ }
+ })
+ }
+}
diff --git a/internal/fleet/agent_policy/models_advanced_settings.go b/internal/fleet/agent_policy/models_advanced_settings.go
new file mode 100644
index 000000000..8e15a4b08
--- /dev/null
+++ b/internal/fleet/agent_policy/models_advanced_settings.go
@@ -0,0 +1,235 @@
+package agent_policy
+
+import (
+ "context"
+
+ "github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils"
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+)
+
+type advancedSettingsModel struct {
+ LoggingLevel types.String `tfsdk:"logging_level"`
+ LoggingToFiles types.Bool `tfsdk:"logging_to_files"`
+ LoggingFilesInterval customtypes.Duration `tfsdk:"logging_files_interval"`
+ LoggingFilesKeepfiles types.Int32 `tfsdk:"logging_files_keepfiles"`
+ LoggingFilesRotateeverybytes types.Int64 `tfsdk:"logging_files_rotateeverybytes"`
+ LoggingMetricsPeriod customtypes.Duration `tfsdk:"logging_metrics_period"`
+ GoMaxProcs types.Int32 `tfsdk:"go_max_procs"`
+ DownloadTimeout customtypes.Duration `tfsdk:"download_timeout"`
+ DownloadTargetDirectory types.String `tfsdk:"download_target_directory"`
+ MonitoringRuntimeExperimental types.String `tfsdk:"monitoring_runtime_experimental"`
+}
+
+// advancedSettingsAttrTypes returns attribute types for advanced_settings pulled from the schema
+func advancedSettingsAttrTypes() map[string]attr.Type {
+ return getSchema().Attributes["advanced_settings"].GetType().(attr.TypeWithAttributeTypes).AttributeTypes()
+}
+
+// advancedSettingsAPIResult is the return type for convertAdvancedSettingsToAPI
+type advancedSettingsAPIResult = struct {
+ AgentDownloadTargetDirectory interface{} `json:"agent_download_target_directory,omitempty"`
+ AgentDownloadTimeout interface{} `json:"agent_download_timeout,omitempty"`
+ AgentInternal interface{} `json:"agent_internal,omitempty"`
+ AgentLimitsGoMaxProcs interface{} `json:"agent_limits_go_max_procs,omitempty"`
+ AgentLoggingFilesInterval interface{} `json:"agent_logging_files_interval,omitempty"`
+ AgentLoggingFilesKeepfiles interface{} `json:"agent_logging_files_keepfiles,omitempty"`
+ AgentLoggingFilesRotateeverybytes interface{} `json:"agent_logging_files_rotateeverybytes,omitempty"`
+ AgentLoggingLevel interface{} `json:"agent_logging_level,omitempty"`
+ AgentLoggingMetricsPeriod interface{} `json:"agent_logging_metrics_period,omitempty"`
+ AgentLoggingToFiles interface{} `json:"agent_logging_to_files,omitempty"`
+ AgentMonitoringRuntimeExperimental interface{} `json:"agent_monitoring_runtime_experimental,omitempty"`
+}
+
+// populateAdvancedSettingsFromAPI populates the advanced settings from API response
+func (model *agentPolicyModel) populateAdvancedSettingsFromAPI(ctx context.Context, data *kbapi.AgentPolicy) diag.Diagnostics {
+ if data.AdvancedSettings == nil {
+ model.AdvancedSettings = types.ObjectNull(advancedSettingsAttrTypes())
+ return nil
+ }
+
+ settings := advancedSettingsModel{}
+
+ // Logging level
+ if data.AdvancedSettings.AgentLoggingLevel != nil {
+ if str, ok := data.AdvancedSettings.AgentLoggingLevel.(string); ok {
+ settings.LoggingLevel = types.StringValue(str)
+ } else {
+ settings.LoggingLevel = types.StringNull()
+ }
+ } else {
+ settings.LoggingLevel = types.StringNull()
+ }
+
+ // Logging to files
+ if data.AdvancedSettings.AgentLoggingToFiles != nil {
+ if b, ok := data.AdvancedSettings.AgentLoggingToFiles.(bool); ok {
+ settings.LoggingToFiles = types.BoolValue(b)
+ } else {
+ settings.LoggingToFiles = types.BoolNull()
+ }
+ } else {
+ settings.LoggingToFiles = types.BoolNull()
+ }
+
+ // Logging files interval
+ if data.AdvancedSettings.AgentLoggingFilesInterval != nil {
+ if str, ok := data.AdvancedSettings.AgentLoggingFilesInterval.(string); ok {
+ settings.LoggingFilesInterval = customtypes.NewDurationValue(str)
+ } else {
+ settings.LoggingFilesInterval = customtypes.NewDurationNull()
+ }
+ } else {
+ settings.LoggingFilesInterval = customtypes.NewDurationNull()
+ }
+
+ // Logging files keepfiles
+ if data.AdvancedSettings.AgentLoggingFilesKeepfiles != nil {
+ if f, ok := data.AdvancedSettings.AgentLoggingFilesKeepfiles.(float64); ok {
+ settings.LoggingFilesKeepfiles = types.Int32Value(int32(f))
+ } else {
+ settings.LoggingFilesKeepfiles = types.Int32Null()
+ }
+ } else {
+ settings.LoggingFilesKeepfiles = types.Int32Null()
+ }
+
+ // Logging files rotateeverybytes
+ if data.AdvancedSettings.AgentLoggingFilesRotateeverybytes != nil {
+ if f, ok := data.AdvancedSettings.AgentLoggingFilesRotateeverybytes.(float64); ok {
+ settings.LoggingFilesRotateeverybytes = types.Int64Value(int64(f))
+ } else {
+ settings.LoggingFilesRotateeverybytes = types.Int64Null()
+ }
+ } else {
+ settings.LoggingFilesRotateeverybytes = types.Int64Null()
+ }
+
+ // Logging metrics period
+ if data.AdvancedSettings.AgentLoggingMetricsPeriod != nil {
+ if str, ok := data.AdvancedSettings.AgentLoggingMetricsPeriod.(string); ok {
+ settings.LoggingMetricsPeriod = customtypes.NewDurationValue(str)
+ } else {
+ settings.LoggingMetricsPeriod = customtypes.NewDurationNull()
+ }
+ } else {
+ settings.LoggingMetricsPeriod = customtypes.NewDurationNull()
+ }
+
+ // Go max procs
+ if data.AdvancedSettings.AgentLimitsGoMaxProcs != nil {
+ if f, ok := data.AdvancedSettings.AgentLimitsGoMaxProcs.(float64); ok {
+ settings.GoMaxProcs = types.Int32Value(int32(f))
+ } else {
+ settings.GoMaxProcs = types.Int32Null()
+ }
+ } else {
+ settings.GoMaxProcs = types.Int32Null()
+ }
+
+ // Download timeout
+ if data.AdvancedSettings.AgentDownloadTimeout != nil {
+ if str, ok := data.AdvancedSettings.AgentDownloadTimeout.(string); ok {
+ settings.DownloadTimeout = customtypes.NewDurationValue(str)
+ } else {
+ settings.DownloadTimeout = customtypes.NewDurationNull()
+ }
+ } else {
+ settings.DownloadTimeout = customtypes.NewDurationNull()
+ }
+
+ // Download target directory
+ if data.AdvancedSettings.AgentDownloadTargetDirectory != nil {
+ if str, ok := data.AdvancedSettings.AgentDownloadTargetDirectory.(string); ok {
+ settings.DownloadTargetDirectory = types.StringValue(str)
+ } else {
+ settings.DownloadTargetDirectory = types.StringNull()
+ }
+ } else {
+ settings.DownloadTargetDirectory = types.StringNull()
+ }
+
+ // Monitoring runtime experimental
+ if data.AdvancedSettings.AgentMonitoringRuntimeExperimental != nil {
+ if str, ok := data.AdvancedSettings.AgentMonitoringRuntimeExperimental.(string); ok {
+ settings.MonitoringRuntimeExperimental = types.StringValue(str)
+ } else {
+ settings.MonitoringRuntimeExperimental = types.StringNull()
+ }
+ } else {
+ settings.MonitoringRuntimeExperimental = types.StringNull()
+ }
+
+ obj, diags := types.ObjectValueFrom(ctx, advancedSettingsAttrTypes(), settings)
+ if diags.HasError() {
+ return diags
+ }
+ model.AdvancedSettings = obj
+ return nil
+}
+
+// convertAdvancedSettingsToAPI converts the advanced settings config to API format
+func (model *agentPolicyModel) convertAdvancedSettingsToAPI(ctx context.Context) *advancedSettingsAPIResult {
+ if !utils.IsKnown(model.AdvancedSettings) {
+ return nil
+ }
+
+ var settings advancedSettingsModel
+ model.AdvancedSettings.As(ctx, &settings, basetypes.ObjectAsOptions{})
+
+ // Check if any values are set
+ hasValues := utils.IsKnown(settings.LoggingLevel) ||
+ utils.IsKnown(settings.LoggingToFiles) ||
+ utils.IsKnown(settings.LoggingFilesInterval) ||
+ utils.IsKnown(settings.LoggingFilesKeepfiles) ||
+ utils.IsKnown(settings.LoggingFilesRotateeverybytes) ||
+ utils.IsKnown(settings.LoggingMetricsPeriod) ||
+ utils.IsKnown(settings.GoMaxProcs) ||
+ utils.IsKnown(settings.DownloadTimeout) ||
+ utils.IsKnown(settings.DownloadTargetDirectory) ||
+ utils.IsKnown(settings.MonitoringRuntimeExperimental)
+
+ if !hasValues {
+ return nil
+ }
+
+ result := &advancedSettingsAPIResult{}
+
+ if utils.IsKnown(settings.LoggingLevel) {
+ result.AgentLoggingLevel = settings.LoggingLevel.ValueString()
+ }
+ if utils.IsKnown(settings.LoggingToFiles) {
+ result.AgentLoggingToFiles = settings.LoggingToFiles.ValueBool()
+ }
+ if utils.IsKnown(settings.LoggingFilesInterval) {
+ result.AgentLoggingFilesInterval = settings.LoggingFilesInterval.ValueString()
+ }
+ if utils.IsKnown(settings.LoggingFilesKeepfiles) {
+ result.AgentLoggingFilesKeepfiles = settings.LoggingFilesKeepfiles.ValueInt32()
+ }
+ if utils.IsKnown(settings.LoggingFilesRotateeverybytes) {
+ result.AgentLoggingFilesRotateeverybytes = settings.LoggingFilesRotateeverybytes.ValueInt64()
+ }
+ if utils.IsKnown(settings.LoggingMetricsPeriod) {
+ result.AgentLoggingMetricsPeriod = settings.LoggingMetricsPeriod.ValueString()
+ }
+ if utils.IsKnown(settings.GoMaxProcs) {
+ result.AgentLimitsGoMaxProcs = settings.GoMaxProcs.ValueInt32()
+ }
+ if utils.IsKnown(settings.DownloadTimeout) {
+ result.AgentDownloadTimeout = settings.DownloadTimeout.ValueString()
+ }
+ if utils.IsKnown(settings.DownloadTargetDirectory) {
+ result.AgentDownloadTargetDirectory = settings.DownloadTargetDirectory.ValueString()
+ }
+ if utils.IsKnown(settings.MonitoringRuntimeExperimental) {
+ result.AgentMonitoringRuntimeExperimental = settings.MonitoringRuntimeExperimental.ValueString()
+ }
+
+ return result
+}
diff --git a/internal/fleet/agent_policy/models_advanced_settings_test.go b/internal/fleet/agent_policy/models_advanced_settings_test.go
new file mode 100644
index 000000000..8c97e90da
--- /dev/null
+++ b/internal/fleet/agent_policy/models_advanced_settings_test.go
@@ -0,0 +1,135 @@
+package agent_policy
+
+import (
+ "context"
+ "testing"
+
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestConvertAdvancedSettingsToAPI(t *testing.T) {
+ ctx := context.Background()
+
+ createAdvancedSettingsObject := func(settings advancedSettingsModel) types.Object {
+ obj, _ := types.ObjectValueFrom(ctx, advancedSettingsAttrTypes(), settings)
+ return obj
+ }
+
+ tests := []struct {
+ name string
+ advancedSettings types.Object
+ wantNil bool
+ checkResult func(t *testing.T, result *advancedSettingsAPIResult)
+ }{
+ {
+ name: "null advanced_settings returns nil",
+ advancedSettings: types.ObjectNull(advancedSettingsAttrTypes()),
+ wantNil: true,
+ },
+ {
+ name: "all null values returns nil",
+ advancedSettings: createAdvancedSettingsObject(advancedSettingsModel{
+ LoggingLevel: types.StringNull(),
+ LoggingToFiles: types.BoolNull(),
+ LoggingFilesInterval: customtypes.NewDurationNull(),
+ LoggingFilesKeepfiles: types.Int32Null(),
+ LoggingFilesRotateeverybytes: types.Int64Null(),
+ LoggingMetricsPeriod: customtypes.NewDurationNull(),
+ GoMaxProcs: types.Int32Null(),
+ DownloadTimeout: customtypes.NewDurationNull(),
+ DownloadTargetDirectory: types.StringNull(),
+ MonitoringRuntimeExperimental: types.StringNull(),
+ }),
+ wantNil: true,
+ },
+ {
+ name: "logging_level set returns value",
+ advancedSettings: createAdvancedSettingsObject(advancedSettingsModel{
+ LoggingLevel: types.StringValue("debug"),
+ LoggingToFiles: types.BoolNull(),
+ LoggingFilesInterval: customtypes.NewDurationNull(),
+ LoggingFilesKeepfiles: types.Int32Null(),
+ LoggingFilesRotateeverybytes: types.Int64Null(),
+ LoggingMetricsPeriod: customtypes.NewDurationNull(),
+ GoMaxProcs: types.Int32Null(),
+ DownloadTimeout: customtypes.NewDurationNull(),
+ DownloadTargetDirectory: types.StringNull(),
+ MonitoringRuntimeExperimental: types.StringNull(),
+ }),
+ wantNil: false,
+ checkResult: func(t *testing.T, result *advancedSettingsAPIResult) {
+ assert.Equal(t, "debug", result.AgentLoggingLevel)
+ assert.Nil(t, result.AgentLoggingToFiles)
+ },
+ },
+ {
+ name: "go_max_procs set returns value",
+ advancedSettings: createAdvancedSettingsObject(advancedSettingsModel{
+ LoggingLevel: types.StringNull(),
+ LoggingToFiles: types.BoolNull(),
+ LoggingFilesInterval: customtypes.NewDurationNull(),
+ LoggingFilesKeepfiles: types.Int32Null(),
+ LoggingFilesRotateeverybytes: types.Int64Null(),
+ LoggingMetricsPeriod: customtypes.NewDurationNull(),
+ GoMaxProcs: types.Int32Value(4),
+ DownloadTimeout: customtypes.NewDurationNull(),
+ DownloadTargetDirectory: types.StringNull(),
+ MonitoringRuntimeExperimental: types.StringNull(),
+ }),
+ wantNil: false,
+ checkResult: func(t *testing.T, result *advancedSettingsAPIResult) {
+ assert.Equal(t, int32(4), result.AgentLimitsGoMaxProcs)
+ },
+ },
+ {
+ name: "multiple values set returns all values",
+ advancedSettings: createAdvancedSettingsObject(advancedSettingsModel{
+ LoggingLevel: types.StringValue("info"),
+ LoggingToFiles: types.BoolValue(true),
+ LoggingFilesInterval: customtypes.NewDurationValue("30s"),
+ LoggingFilesKeepfiles: types.Int32Value(7),
+ LoggingFilesRotateeverybytes: types.Int64Value(10485760),
+ LoggingMetricsPeriod: customtypes.NewDurationValue("1m"),
+ GoMaxProcs: types.Int32Value(2),
+ DownloadTimeout: customtypes.NewDurationValue("2h"),
+ DownloadTargetDirectory: types.StringValue("/tmp/elastic"),
+ MonitoringRuntimeExperimental: types.StringValue(""),
+ }),
+ wantNil: false,
+ checkResult: func(t *testing.T, result *advancedSettingsAPIResult) {
+ assert.Equal(t, "info", result.AgentLoggingLevel)
+ assert.Equal(t, true, result.AgentLoggingToFiles)
+ assert.Equal(t, "30s", result.AgentLoggingFilesInterval)
+ assert.Equal(t, int32(7), result.AgentLoggingFilesKeepfiles)
+ assert.Equal(t, int64(10485760), result.AgentLoggingFilesRotateeverybytes)
+ assert.Equal(t, "1m", result.AgentLoggingMetricsPeriod)
+ assert.Equal(t, int32(2), result.AgentLimitsGoMaxProcs)
+ assert.Equal(t, "2h", result.AgentDownloadTimeout)
+ assert.Equal(t, "/tmp/elastic", result.AgentDownloadTargetDirectory)
+ assert.Equal(t, "", result.AgentMonitoringRuntimeExperimental)
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ model := &agentPolicyModel{
+ AdvancedSettings: tt.advancedSettings,
+ }
+
+ got := model.convertAdvancedSettingsToAPI(ctx)
+
+ if tt.wantNil {
+ assert.Nil(t, got)
+ return
+ }
+
+ assert.NotNil(t, got)
+ if tt.checkResult != nil {
+ tt.checkResult(t, got)
+ }
+ })
+ }
+}
diff --git a/internal/fleet/agent_policy/models_test.go b/internal/fleet/agent_policy/models_test.go
index bc88a7aa7..a49b30610 100644
--- a/internal/fleet/agent_policy/models_test.go
+++ b/internal/fleet/agent_policy/models_test.go
@@ -1,10 +1,8 @@
package agent_policy
import (
- "context"
"testing"
- "github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stretchr/testify/assert"
)
@@ -145,252 +143,3 @@ func TestConvertHostNameFormatToAgentFeature(t *testing.T) {
})
}
}
-
-func TestConvertHttpMonitoringEndpointToAPI(t *testing.T) {
- ctx := context.Background()
-
- // Helper to create types.Object from httpMonitoringEndpointModel
- createHttpEndpointObject := func(m httpMonitoringEndpointModel) types.Object {
- obj, _ := types.ObjectValueFrom(ctx, httpMonitoringEndpointAttrTypes(), m)
- return obj
- }
-
- // Helper to create types.Object from advancedMonitoringOptionsModel
- createAmoObject := func(httpEndpoint types.Object) types.Object {
- amo := advancedMonitoringOptionsModel{
- HttpMonitoringEndpoint: httpEndpoint,
- Diagnostics: types.ObjectNull(diagnosticsAttrTypes()),
- }
- obj, _ := types.ObjectValueFrom(ctx, advancedMonitoringOptionsAttrTypes(), amo)
- return obj
- }
-
- tests := []struct {
- name string
- amo types.Object
- wantHttp bool
- wantPprof bool
- wantPprofValue bool
- }{
- {
- name: "null advanced monitoring options returns nil",
- amo: types.ObjectNull(advancedMonitoringOptionsAttrTypes()),
- wantHttp: false,
- },
- {
- name: "null http monitoring endpoint returns nil",
- amo: createAmoObject(types.ObjectNull(httpMonitoringEndpointAttrTypes())),
- wantHttp: false,
- },
- {
- name: "default values returns nil (omit from payload)",
- amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{
- Enabled: types.BoolValue(false),
- Host: types.StringValue("localhost"),
- Port: types.Int32Value(6791),
- BufferEnabled: types.BoolValue(false),
- PprofEnabled: types.BoolValue(false),
- })),
- wantHttp: false,
- },
- {
- name: "enabled http endpoint returns values",
- amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{
- Enabled: types.BoolValue(true),
- Host: types.StringValue("localhost"),
- Port: types.Int32Value(6791),
- BufferEnabled: types.BoolValue(false),
- PprofEnabled: types.BoolValue(false),
- })),
- wantHttp: true,
- wantPprof: true,
- wantPprofValue: false,
- },
- {
- name: "custom port returns values",
- amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{
- Enabled: types.BoolValue(false),
- Host: types.StringValue("localhost"),
- Port: types.Int32Value(8080),
- BufferEnabled: types.BoolValue(false),
- PprofEnabled: types.BoolValue(false),
- })),
- wantHttp: true,
- wantPprof: true,
- wantPprofValue: false,
- },
- {
- name: "pprof enabled returns values",
- amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{
- Enabled: types.BoolValue(true),
- Host: types.StringValue("localhost"),
- Port: types.Int32Value(6791),
- BufferEnabled: types.BoolValue(false),
- PprofEnabled: types.BoolValue(true),
- })),
- wantHttp: true,
- wantPprof: true,
- wantPprofValue: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- model := &agentPolicyModel{
- AdvancedMonitoringOptions: tt.amo,
- }
-
- gotHttp, gotPprof := model.convertHttpMonitoringEndpointToAPI(ctx)
-
- if !tt.wantHttp {
- assert.Nil(t, gotHttp)
- assert.Nil(t, gotPprof)
- return
- }
-
- assert.NotNil(t, gotHttp)
- if tt.wantPprof {
- assert.NotNil(t, gotPprof)
- assert.Equal(t, tt.wantPprofValue, *gotPprof)
- }
- })
- }
-}
-
-func TestConvertDiagnosticsToAPI(t *testing.T) {
- ctx := context.Background()
-
- // Helper to create types.Object from rateLimitsModel
- createRateLimitsObject := func(m rateLimitsModel) types.Object {
- obj, _ := types.ObjectValueFrom(ctx, rateLimitsAttrTypes(), m)
- return obj
- }
-
- // Helper to create types.Object from fileUploaderModel
- createFileUploaderObject := func(m fileUploaderModel) types.Object {
- obj, _ := types.ObjectValueFrom(ctx, fileUploaderAttrTypes(), m)
- return obj
- }
-
- // Helper to create types.Object from diagnosticsModel
- createDiagnosticsObject := func(rateLimits, fileUploader types.Object) types.Object {
- diag := diagnosticsModel{
- RateLimits: rateLimits,
- FileUploader: fileUploader,
- }
- obj, _ := types.ObjectValueFrom(ctx, diagnosticsAttrTypes(), diag)
- return obj
- }
-
- // Helper to create types.Object from advancedMonitoringOptionsModel
- createAmoObject := func(diagnostics types.Object) types.Object {
- amo := advancedMonitoringOptionsModel{
- HttpMonitoringEndpoint: types.ObjectNull(httpMonitoringEndpointAttrTypes()),
- Diagnostics: diagnostics,
- }
- obj, _ := types.ObjectValueFrom(ctx, advancedMonitoringOptionsAttrTypes(), amo)
- return obj
- }
-
- tests := []struct {
- name string
- amo types.Object
- wantDiag bool
- wantRateLimits bool
- wantUploader bool
- }{
- {
- name: "null advanced monitoring options returns nil",
- amo: types.ObjectNull(advancedMonitoringOptionsAttrTypes()),
- wantDiag: false,
- },
- {
- name: "null diagnostics returns nil",
- amo: createAmoObject(types.ObjectNull(diagnosticsAttrTypes())),
- wantDiag: false,
- },
- {
- name: "default rate limits values returns nil (omit from payload)",
- amo: createAmoObject(createDiagnosticsObject(
- createRateLimitsObject(rateLimitsModel{
- Interval: customtypes.NewDurationValue("1m"),
- Burst: types.Int32Value(1),
- }),
- types.ObjectNull(fileUploaderAttrTypes()),
- )),
- wantDiag: false,
- },
- {
- name: "default uploader values returns nil (omit from payload)",
- amo: createAmoObject(createDiagnosticsObject(
- types.ObjectNull(rateLimitsAttrTypes()),
- createFileUploaderObject(fileUploaderModel{
- InitDuration: customtypes.NewDurationValue("1s"),
- BackoffDuration: customtypes.NewDurationValue("1m"),
- MaxRetries: types.Int32Value(10),
- }),
- )),
- wantDiag: false,
- },
- {
- name: "custom rate limits interval returns values",
- amo: createAmoObject(createDiagnosticsObject(
- createRateLimitsObject(rateLimitsModel{
- Interval: customtypes.NewDurationValue("2m"),
- Burst: types.Int32Value(1),
- }),
- types.ObjectNull(fileUploaderAttrTypes()),
- )),
- wantDiag: true,
- wantRateLimits: true,
- },
- {
- name: "custom rate limits burst returns values",
- amo: createAmoObject(createDiagnosticsObject(
- createRateLimitsObject(rateLimitsModel{
- Interval: customtypes.NewDurationValue("1m"),
- Burst: types.Int32Value(5),
- }),
- types.ObjectNull(fileUploaderAttrTypes()),
- )),
- wantDiag: true,
- wantRateLimits: true,
- },
- {
- name: "custom uploader max_retries returns values",
- amo: createAmoObject(createDiagnosticsObject(
- types.ObjectNull(rateLimitsAttrTypes()),
- createFileUploaderObject(fileUploaderModel{
- InitDuration: customtypes.NewDurationValue("1s"),
- BackoffDuration: customtypes.NewDurationValue("1m"),
- MaxRetries: types.Int32Value(20),
- }),
- )),
- wantDiag: true,
- wantUploader: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- model := &agentPolicyModel{
- AdvancedMonitoringOptions: tt.amo,
- }
-
- got := model.convertDiagnosticsToAPI(ctx)
-
- if !tt.wantDiag {
- assert.Nil(t, got)
- return
- }
-
- assert.NotNil(t, got)
- if tt.wantRateLimits {
- assert.NotNil(t, got.Limit)
- }
- if tt.wantUploader {
- assert.NotNil(t, got.Uploader)
- }
- })
- }
-}
diff --git a/internal/fleet/agent_policy/resource.go b/internal/fleet/agent_policy/resource.go
index cd88d47fa..3cda6999e 100644
--- a/internal/fleet/agent_policy/resource.go
+++ b/internal/fleet/agent_policy/resource.go
@@ -27,6 +27,7 @@ var (
MinVersionRequiredVersions = version.Must(version.NewVersion("9.1.0"))
MinVersionAgentFeatures = version.Must(version.NewVersion("8.7.0"))
MinVersionAdvancedMonitoring = version.Must(version.NewVersion("8.16.0"))
+ MinVersionAdvancedSettings = version.Must(version.NewVersion("8.17.0"))
)
// NewResource is a helper function to simplify the provider implementation.
@@ -93,6 +94,11 @@ func (r *agentPolicyResource) buildFeatures(ctx context.Context) (features, diag
return features{}, diagutil.FrameworkDiagsFromSDK(diags)
}
+ supportsAdvancedSettings, diags := r.client.EnforceMinVersion(ctx, MinVersionAdvancedSettings)
+ if diags.HasError() {
+ return features{}, diagutil.FrameworkDiagsFromSDK(diags)
+ }
+
return features{
SupportsGlobalDataTags: supportsGDT,
SupportsSupportsAgentless: supportsSupportsAgentless,
@@ -102,5 +108,6 @@ func (r *agentPolicyResource) buildFeatures(ctx context.Context) (features, diag
SupportsRequiredVersions: supportsRequiredVersions,
SupportsAgentFeatures: supportsAgentFeatures,
SupportsAdvancedMonitoring: supportsAdvancedMonitoring,
+ SupportsAdvancedSettings: supportsAdvancedSettings,
}, nil
}
diff --git a/internal/fleet/agent_policy/schema.go b/internal/fleet/agent_policy/schema.go
index 043c552ae..0194c32ad 100644
--- a/internal/fleet/agent_policy/schema.go
+++ b/internal/fleet/agent_policy/schema.go
@@ -15,9 +15,12 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
+ "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/setplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
@@ -117,12 +120,18 @@ func getSchema() schema.Schema {
Computed: true,
Optional: true,
CustomType: customtypes.DurationType{},
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
},
"unenrollment_timeout": schema.StringAttribute{
Description: "The unenrollment timeout for the agent policy. If an agent is inactive for this period, it will be automatically unenrolled. Supports duration strings (e.g., '30s', '2m', '1h').",
Computed: true,
Optional: true,
CustomType: customtypes.DurationType{},
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
},
"global_data_tags": schema.MapNestedAttribute{
Description: "User-defined data tags to apply to all inputs. Values can be strings (string_value) or numbers (number_value) but not both. Example -- key1 = {string_value = value1}, key2 = {number_value = 42}",
@@ -158,6 +167,9 @@ func getSchema() schema.Schema {
ElementType: types.StringType,
Optional: true,
Computed: true,
+ PlanModifiers: []planmodifier.Set{
+ setplanmodifier.UseStateForUnknown(),
+ },
},
"required_versions": schema.MapAttribute{
Description: "Map of agent versions to target percentages for automatic upgrade. The key is the target version and the value is the percentage of agents to upgrade to that version.",
@@ -173,15 +185,110 @@ func getSchema() schema.Schema {
),
},
},
+ "advanced_settings": schema.SingleNestedAttribute{
+ Description: "Advanced agent settings for logging, resource limits, and downloads. These settings configure the behavior of Elastic Agents enrolled in this policy.",
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.UseStateForUnknown(),
+ },
+ Attributes: map[string]schema.Attribute{
+ "logging_level": schema.StringAttribute{
+ Description: "Logging level for the agent. Valid values: debug, info, warning, error.",
+ Optional: true,
+ Computed: true,
+ Default: stringdefault.StaticString("info"),
+ Validators: []validator.String{
+ stringvalidator.OneOf("debug", "info", "warning", "error"),
+ },
+ },
+ "logging_to_files": schema.BoolAttribute{
+ Description: "Enable logging to files.",
+ Optional: true,
+ Computed: true,
+ Default: booldefault.StaticBool(true),
+ },
+ "logging_files_interval": schema.StringAttribute{
+ Description: "Interval for log file rotation (e.g., '30s', '1m', '1h').",
+ Optional: true,
+ Computed: true,
+ CustomType: customtypes.DurationType{},
+ Default: stringdefault.StaticString("30s"),
+ },
+ "logging_files_keepfiles": schema.Int32Attribute{
+ Description: "Number of rotated log files to keep.",
+ Optional: true,
+ Computed: true,
+ Default: int32default.StaticInt32(7),
+ Validators: []validator.Int32{
+ int32validator.AtLeast(0),
+ },
+ },
+ "logging_files_rotateeverybytes": schema.Int64Attribute{
+ Description: "Rotate log files when they reach this size in bytes.",
+ Optional: true,
+ Computed: true,
+ Default: int64default.StaticInt64(10485760),
+ },
+ "logging_metrics_period": schema.StringAttribute{
+ Description: "Period for logging agent metrics (e.g., '30s', '1m').",
+ Optional: true,
+ Computed: true,
+ CustomType: customtypes.DurationType{},
+ Default: stringdefault.StaticString("30s"),
+ },
+ "go_max_procs": schema.Int32Attribute{
+ Description: "Maximum number of CPUs that the agent can use (GOMAXPROCS). Set to 0 to use all available CPUs.",
+ Optional: true,
+ Computed: true,
+ Default: int32default.StaticInt32(0),
+ Validators: []validator.Int32{
+ int32validator.AtLeast(0),
+ },
+ },
+ "download_timeout": schema.StringAttribute{
+ Description: "Timeout for downloading agent updates (e.g., '2h', '30m').",
+ Optional: true,
+ Computed: true,
+ CustomType: customtypes.DurationType{},
+ Default: stringdefault.StaticString("2h"),
+ },
+ "download_target_directory": schema.StringAttribute{
+ Description: "Target directory for downloading agent updates.",
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "monitoring_runtime_experimental": schema.StringAttribute{
+ Description: "Experimental runtime monitoring mode. Valid values: '' (empty string to disable), 'process', 'otel'.",
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ Validators: []validator.String{
+ stringvalidator.OneOf("", "process", "otel"),
+ },
+ },
+ },
+ },
"advanced_monitoring_options": schema.SingleNestedAttribute{
Description: "Advanced monitoring options for the agent policy. Includes HTTP monitoring endpoint configuration and diagnostic settings.",
Optional: true,
Computed: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.UseStateForUnknown(),
+ },
Attributes: map[string]schema.Attribute{
"http_monitoring_endpoint": schema.SingleNestedAttribute{
Description: "HTTP monitoring endpoint configuration for agent health checks and liveness probes.",
Optional: true,
Computed: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.UseStateForUnknown(),
+ },
Attributes: map[string]schema.Attribute{
"enabled": schema.BoolAttribute{
Description: "Enable the HTTP monitoring endpoint. When enabled, exposes a /liveness endpoint for health checks.",
@@ -222,11 +329,17 @@ func getSchema() schema.Schema {
Description: "Diagnostic settings for rate limiting and file upload behavior.",
Optional: true,
Computed: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.UseStateForUnknown(),
+ },
Attributes: map[string]schema.Attribute{
"rate_limits": schema.SingleNestedAttribute{
Description: "Rate limiting configuration for diagnostics requests from Fleet.",
Optional: true,
Computed: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.UseStateForUnknown(),
+ },
Attributes: map[string]schema.Attribute{
"interval": schema.StringAttribute{
Description: "Rate limiting interval for diagnostics requests (e.g., '1m', '30s').",
@@ -247,6 +360,9 @@ func getSchema() schema.Schema {
Description: "Diagnostic file upload retry configuration.",
Optional: true,
Computed: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.UseStateForUnknown(),
+ },
Attributes: map[string]schema.Attribute{
"init_duration": schema.StringAttribute{
Description: "Initial duration before the first retry attempt (e.g., '1s', '500ms').",
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_advanced_monitoring/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_advanced_monitoring/main.tf
index f65310ff3..4fbbb521c 100644
--- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_advanced_monitoring/main.tf
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_advanced_monitoring/main.tf
@@ -10,5 +10,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" {
monitor_logs = true
monitor_metrics = true
skip_destroy = var.skip_destroy
+
+ # advanced_monitoring_options removed entirely - UseStateForUnknown preserves state
}
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf
similarity index 56%
rename from internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/main.tf
rename to internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf
index f2b046008..43fd27bb4 100644
--- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/main.tf
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf
@@ -6,18 +6,17 @@ provider "elasticstack" {
resource "elasticstack_fleet_agent_policy" "test_policy" {
name = var.policy_name
namespace = "default"
- description = "Test Agent Policy - Diagnostics Removed"
+ description = "Test Agent Policy with Default Advanced Monitoring"
monitor_logs = true
monitor_metrics = true
skip_destroy = var.skip_destroy
+ # Empty nested blocks - schema defaults are applied for leaf attributes
advanced_monitoring_options = {
- http_monitoring_endpoint = {
- enabled = true
- host = "0.0.0.0"
- port = 8080
- buffer_enabled = true
- pprof_enabled = true
+ http_monitoring_endpoint = {}
+ diagnostics = {
+ rate_limits = {}
+ file_uploader = {}
}
}
}
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/variables.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/variables.tf
similarity index 100%
rename from internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/variables.tf
rename to internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/variables.tf
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/create_with_logging/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/create_with_logging/main.tf
new file mode 100644
index 000000000..745d3c28c
--- /dev/null
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/create_with_logging/main.tf
@@ -0,0 +1,19 @@
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_fleet_agent_policy" "test_policy" {
+ name = var.policy_name
+ namespace = "default"
+ description = "Test Agent Policy with Advanced Settings"
+ monitor_logs = true
+ monitor_metrics = true
+
+ advanced_settings = {
+ logging_level = "debug"
+ logging_to_files = true
+ go_max_procs = 2
+ }
+}
+
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/create_with_logging/variables.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/create_with_logging/variables.tf
new file mode 100644
index 000000000..4f40a3ea5
--- /dev/null
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/create_with_logging/variables.tf
@@ -0,0 +1,4 @@
+variable "policy_name" {
+ type = string
+}
+
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf
new file mode 100644
index 000000000..1e318d76f
--- /dev/null
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf
@@ -0,0 +1,15 @@
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_fleet_agent_policy" "test_policy" {
+ name = var.policy_name
+ namespace = "default"
+ description = "Test Agent Policy without Advanced Settings"
+ monitor_logs = true
+ monitor_metrics = true
+
+ # advanced_settings removed entirely - UseStateForUnknown preserves state
+}
+
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/variables.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/variables.tf
new file mode 100644
index 000000000..4f40a3ea5
--- /dev/null
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/variables.tf
@@ -0,0 +1,4 @@
+variable "policy_name" {
+ type = string
+}
+
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/main.tf
new file mode 100644
index 000000000..4487bd99a
--- /dev/null
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/main.tf
@@ -0,0 +1,16 @@
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_fleet_agent_policy" "test_policy" {
+ name = var.policy_name
+ namespace = "default"
+ description = "Test Agent Policy with Default Advanced Settings"
+ monitor_logs = true
+ monitor_metrics = true
+
+ # Empty block - schema defaults are applied for flat attributes
+ advanced_settings = {}
+}
+
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/variables.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/variables.tf
new file mode 100644
index 000000000..4f40a3ea5
--- /dev/null
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/variables.tf
@@ -0,0 +1,4 @@
+variable "policy_name" {
+ type = string
+}
+
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/update_settings/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/update_settings/main.tf
new file mode 100644
index 000000000..d2173b781
--- /dev/null
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/update_settings/main.tf
@@ -0,0 +1,22 @@
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_fleet_agent_policy" "test_policy" {
+ name = var.policy_name
+ namespace = "default"
+ description = "Test Agent Policy with Advanced Settings"
+ monitor_logs = true
+ monitor_metrics = true
+
+ advanced_settings = {
+ logging_level = "info"
+ logging_to_files = true
+ logging_files_keepfiles = 7
+ logging_files_rotateeverybytes = 10485760
+ go_max_procs = 4
+ download_target_directory = "/tmp/elastic-agent"
+ }
+}
+
diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/update_settings/variables.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/update_settings/variables.tf
new file mode 100644
index 000000000..4f40a3ea5
--- /dev/null
+++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/update_settings/variables.tf
@@ -0,0 +1,4 @@
+variable "policy_name" {
+ type = string
+}
+
diff --git a/internal/fleet/agent_policy/version_test.go b/internal/fleet/agent_policy/version_test.go
index 1f876d3b4..f4840bbaa 100644
--- a/internal/fleet/agent_policy/version_test.go
+++ b/internal/fleet/agent_policy/version_test.go
@@ -309,6 +309,96 @@ func TestAdvancedMonitoringVersionValidation(t *testing.T) {
}
}
+func TestAdvancedSettingsVersionValidation(t *testing.T) {
+ ctx := context.Background()
+
+ // Create advanced_settings object with some values set
+ advancedSettings, _ := types.ObjectValueFrom(ctx, advancedSettingsAttrTypes(), advancedSettingsModel{
+ LoggingLevel: types.StringValue("debug"),
+ LoggingToFiles: types.BoolValue(true),
+ LoggingFilesInterval: customtypes.NewDurationNull(),
+ LoggingFilesKeepfiles: types.Int32Value(7),
+ LoggingFilesRotateeverybytes: types.Int64Null(),
+ LoggingMetricsPeriod: customtypes.NewDurationNull(),
+ GoMaxProcs: types.Int32Value(4),
+ DownloadTimeout: customtypes.NewDurationNull(),
+ DownloadTargetDirectory: types.StringNull(),
+ MonitoringRuntimeExperimental: types.StringNull(),
+ })
+
+ // Test case where advanced_settings is not supported (older version)
+ model := &agentPolicyModel{
+ Name: types.StringValue("test"),
+ Namespace: types.StringValue("default"),
+ AdvancedSettings: advancedSettings,
+ }
+
+ // Create features with advanced_settings NOT supported
+ feat := features{
+ SupportsAdvancedSettings: false,
+ }
+
+ // Test toAPICreateModel - should return error when advanced_settings is used but not supported
+ _, diags := model.toAPICreateModel(ctx, feat)
+ if !diags.HasError() {
+ t.Error("Expected error when using advanced_settings on unsupported version, but got none")
+ }
+
+ // Check that the error message contains the expected text
+ found := false
+ for _, diag := range diags {
+ if diag.Summary() == "Unsupported Elasticsearch version" {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Error("Expected 'Unsupported Elasticsearch version' error, but didn't find it")
+ }
+
+ // Test toAPIUpdateModel - should return error when advanced_settings is used but not supported
+ _, diags = model.toAPIUpdateModel(ctx, feat, nil)
+ if !diags.HasError() {
+ t.Error("Expected error when using advanced_settings on unsupported version in update, but got none")
+ }
+
+ // Test case where advanced_settings IS supported (newer version)
+ featSupported := features{
+ SupportsAdvancedSettings: true,
+ }
+
+ // Test toAPICreateModel - should NOT return error when advanced_settings is supported
+ _, diags = model.toAPICreateModel(ctx, featSupported)
+ if diags.HasError() {
+ t.Errorf("Did not expect error when using advanced_settings on supported version: %v", diags)
+ }
+
+ // Test toAPIUpdateModel - should NOT return error when advanced_settings is supported
+ _, diags = model.toAPIUpdateModel(ctx, featSupported, nil)
+ if diags.HasError() {
+ t.Errorf("Did not expect error when using advanced_settings on supported version in update: %v", diags)
+ }
+
+ // Test case where advanced_settings is not set (should not cause validation errors)
+ modelWithoutAdvancedSettings := &agentPolicyModel{
+ Name: types.StringValue("test"),
+ Namespace: types.StringValue("default"),
+ // AdvancedSettings is not set (null/unknown)
+ }
+
+ // Test toAPICreateModel - should NOT return error when advanced_settings is not set, even on unsupported version
+ _, diags = modelWithoutAdvancedSettings.toAPICreateModel(ctx, feat)
+ if diags.HasError() {
+ t.Errorf("Did not expect error when advanced_settings is not set: %v", diags)
+ }
+
+ // Test toAPIUpdateModel - should NOT return error when advanced_settings is not set, even on unsupported version
+ _, diags = modelWithoutAdvancedSettings.toAPIUpdateModel(ctx, feat, nil)
+ if diags.HasError() {
+ t.Errorf("Did not expect error when advanced_settings is not set in update: %v", diags)
+ }
+}
+
func TestMinVersionSpaceIds(t *testing.T) {
// Test that the MinVersionSpaceIds constant is set correctly
expected := "9.1.0"