From f58d95102cb1814363ac8f807bfbe4f84a169933 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Thu, 11 Dec 2025 17:23:26 +0100 Subject: [PATCH 01/24] Support advanced settings in fleet policy --- CHANGELOG.md | 4 + internal/fleet/agent_policy/acc_test.go | 59 ++++++ internal/fleet/agent_policy/models.go | 233 +++++++++++++++++++++ internal/fleet/agent_policy/models_test.go | 127 +++++++++++ internal/fleet/agent_policy/schema.go | 69 ++++++ 5 files changed, 492 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5fd27177..d2ab15ccd 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 ([#1116](https://github.com/elastic/terraform-provider-elasticstack/issues/1116)) + ### Breaking changes #### `elasticstack_fleet_integration_policy` input block has changed to a map attribute. diff --git a/internal/fleet/agent_policy/acc_test.go b/internal/fleet/agent_policy/acc_test.go index e35b75030..b4db096f7 100644 --- a/internal/fleet/agent_policy/acc_test.go +++ b/internal/fleet/agent_policy/acc_test.go @@ -662,3 +662,62 @@ 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(minVersionAgentPolicy), + 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(minVersionAgentPolicy), + 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: Remove settings + { + ProtoV6ProviderFactories: acctest.Providers, + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionAgentPolicy), + 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"), + ), + }, + }, + }) +} diff --git a/internal/fleet/agent_policy/models.go b/internal/fleet/agent_policy/models.go index fa591e453..ad72edeaf 100644 --- a/internal/fleet/agent_policy/models.go +++ b/internal/fleet/agent_policy/models.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) const ( @@ -47,6 +48,19 @@ type globalDataTagsItemModel struct { NumberValue types.Float32 `tfsdk:"number_value"` } +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.Bool `tfsdk:"monitoring_runtime_experimental"` +} + type agentPolicyModel struct { ID types.String `tfsdk:"id"` PolicyID types.String `tfsdk:"policy_id"` @@ -68,6 +82,7 @@ type agentPolicyModel struct { GlobalDataTags types.Map `tfsdk:"global_data_tags"` //> globalDataTagsModel SpaceIds types.Set `tfsdk:"space_ids"` RequiredVersions types.Map `tfsdk:"required_versions"` + AdvancedSettings types.Object `tfsdk:"advanced_settings"` } func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.AgentPolicy) diag.Diagnostics { @@ -187,6 +202,11 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi. model.RequiredVersions = types.MapNull(types.Int32Type) } + // Handle advanced_settings + if diags := model.populateAdvancedSettingsFromAPI(ctx, data); diags.HasError() { + return diags + } + return nil } @@ -428,6 +448,9 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat featur } } + // Handle advanced_settings + body.AdvancedSettings = model.convertAdvancedSettingsToAPI(ctx) + return body, nil } @@ -555,6 +578,9 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat featur body.AgentFeatures = &existingFeatures } + // Handle advanced_settings + body.AdvancedSettings = model.convertAdvancedSettingsToAPI(ctx) + return body, nil } @@ -605,3 +631,210 @@ func mergeAgentFeature(existing []apiAgentFeature, newFeature *apiAgentFeature) return &result } + +// 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() +} + +// 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 b, ok := data.AdvancedSettings.AgentMonitoringRuntimeExperimental.(bool); ok { + settings.MonitoringRuntimeExperimental = types.BoolValue(b) + } else { + settings.MonitoringRuntimeExperimental = types.BoolNull() + } + } else { + settings.MonitoringRuntimeExperimental = types.BoolNull() + } + + obj, diags := types.ObjectValueFrom(ctx, advancedSettingsAttrTypes(), settings) + if diags.HasError() { + return diags + } + model.AdvancedSettings = obj + return nil +} + +// 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"` + 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"` +} + +// 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.ValueBool() + } + + return result +} diff --git a/internal/fleet/agent_policy/models_test.go b/internal/fleet/agent_policy/models_test.go index a49b30610..656fabec3 100644 --- a/internal/fleet/agent_policy/models_test.go +++ b/internal/fleet/agent_policy/models_test.go @@ -1,8 +1,10 @@ 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" ) @@ -143,3 +145,128 @@ func TestConvertHostNameFormatToAgentFeature(t *testing.T) { }) } } + +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.BoolNull(), + }), + 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.BoolNull(), + }), + 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.BoolNull(), + }), + 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.BoolValue(false), + }), + 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, false, 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/schema.go b/internal/fleet/agent_policy/schema.go index f6b762c98..e29f11e15 100644 --- a/internal/fleet/agent_policy/schema.go +++ b/internal/fleet/agent_policy/schema.go @@ -172,6 +172,75 @@ 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, + 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, + Validators: []validator.String{ + stringvalidator.OneOf("debug", "info", "warning", "error"), + }, + }, + "logging_to_files": schema.BoolAttribute{ + Description: "Enable logging to files.", + Optional: true, + Computed: true, + }, + "logging_files_interval": schema.StringAttribute{ + Description: "Interval for log file rotation (e.g., '30s', '1m', '1h').", + Optional: true, + Computed: true, + CustomType: customtypes.DurationType{}, + }, + "logging_files_keepfiles": schema.Int32Attribute{ + Description: "Number of rotated log files to keep.", + Optional: true, + Computed: true, + 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, + }, + "logging_metrics_period": schema.StringAttribute{ + Description: "Period for logging agent metrics (e.g., '30s', '1m').", + Optional: true, + Computed: true, + CustomType: customtypes.DurationType{}, + }, + "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, + 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{}, + }, + "download_target_directory": schema.StringAttribute{ + Description: "Target directory for downloading agent updates.", + Optional: true, + Computed: true, + }, + "monitoring_runtime_experimental": schema.BoolAttribute{ + Description: "Enable experimental runtime monitoring.", + Optional: true, + Computed: true, + }, + }, + }, }} } func getGlobalDataTagsAttrTypes() attr.Type { From aefa85154d0e62179739c5c09c4cc9b842edb96a Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Thu, 11 Dec 2025 17:35:38 +0100 Subject: [PATCH 02/24] Update docs --- docs/resources/fleet_agent_policy.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/resources/fleet_agent_policy.md b/docs/resources/fleet_agent_policy.md index c35705fa7..88680d664 100644 --- a/docs/resources/fleet_agent_policy.md +++ b/docs/resources/fleet_agent_policy.md @@ -48,6 +48,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { ### Optional +- `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. @@ -70,6 +71,23 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { - `id` (String) The ID of this resource. + +### 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` (Boolean) Enable experimental runtime monitoring. + + ### Nested Schema for `global_data_tags` From 68a0a1638acc2e8731d075507a9034a6999e515b Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Thu, 11 Dec 2025 17:53:12 +0100 Subject: [PATCH 03/24] test configs --- .../create_with_logging/main.tf | 19 ++++++++++++++++ .../create_with_logging/variables.tf | 4 ++++ .../remove_settings/main.tf | 13 +++++++++++ .../remove_settings/variables.tf | 4 ++++ .../update_settings/main.tf | 22 +++++++++++++++++++ .../update_settings/variables.tf | 4 ++++ 6 files changed, 66 insertions(+) create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/create_with_logging/main.tf create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/create_with_logging/variables.tf create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/variables.tf create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/update_settings/main.tf create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/update_settings/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..5bc9fee00 --- /dev/null +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf @@ -0,0 +1,13 @@ +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 +} + 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/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 +} + From 6b5aab33b372ddeedcc03d09e73bccf7cb7d5e30 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 13:27:14 +0100 Subject: [PATCH 04/24] fmt --- internal/fleet/agent_policy/models_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/fleet/agent_policy/models_test.go b/internal/fleet/agent_policy/models_test.go index 6e1e54e26..46fa95abd 100644 --- a/internal/fleet/agent_policy/models_test.go +++ b/internal/fleet/agent_policy/models_test.go @@ -519,5 +519,3 @@ func TestConvertDiagnosticsToAPI(t *testing.T) { }) } } - - From b6ca114777122b8244316827eef8c470e31c3fa0 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 13:37:16 +0100 Subject: [PATCH 05/24] Update agent policy tests to use the new minimum version for advanced settings --- internal/fleet/agent_policy/acc_test.go | 6 +++--- internal/fleet/agent_policy/resource.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/fleet/agent_policy/acc_test.go b/internal/fleet/agent_policy/acc_test.go index 42a38bc6d..e55222c78 100644 --- a/internal/fleet/agent_policy/acc_test.go +++ b/internal/fleet/agent_policy/acc_test.go @@ -673,7 +673,7 @@ func TestAccResourceAgentPolicyWithAdvancedSettings(t *testing.T) { // Step 1: Create with logging settings { ProtoV6ProviderFactories: acctest.Providers, - SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionAgentPolicy), + SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedSettings), ConfigDirectory: acctest.NamedTestCaseDirectory("create_with_logging"), ConfigVariables: config.Variables{ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)), @@ -689,7 +689,7 @@ func TestAccResourceAgentPolicyWithAdvancedSettings(t *testing.T) { // Step 2: Update settings { ProtoV6ProviderFactories: acctest.Providers, - SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionAgentPolicy), + SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedSettings), ConfigDirectory: acctest.NamedTestCaseDirectory("update_settings"), ConfigVariables: config.Variables{ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)), @@ -708,7 +708,7 @@ func TestAccResourceAgentPolicyWithAdvancedSettings(t *testing.T) { // Step 3: Remove settings { ProtoV6ProviderFactories: acctest.Providers, - SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionAgentPolicy), + SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAdvancedSettings), ConfigDirectory: acctest.NamedTestCaseDirectory("remove_settings"), ConfigVariables: config.Variables{ "policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)), diff --git a/internal/fleet/agent_policy/resource.go b/internal/fleet/agent_policy/resource.go index cd88d47fa..f3c7c037b 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. From c1d287a4b1f9f69c0f070bd592a629017654f92b Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 14:20:26 +0100 Subject: [PATCH 06/24] Add support for advanced settings validation in agent policy --- internal/fleet/agent_policy/models.go | 28 ++++++- internal/fleet/agent_policy/resource.go | 6 ++ internal/fleet/agent_policy/version_test.go | 90 +++++++++++++++++++++ 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/internal/fleet/agent_policy/models.go b/internal/fleet/agent_policy/models.go index 88e855cd8..0edab4f78 100644 --- a/internal/fleet/agent_policy/models.go +++ b/internal/fleet/agent_policy/models.go @@ -43,6 +43,7 @@ type features struct { SupportsRequiredVersions bool SupportsAgentFeatures bool SupportsAdvancedMonitoring bool + SupportsAdvancedSettings bool } type globalDataTagsItemModel struct { @@ -657,7 +658,19 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat featur } // Handle advanced_settings - body.AdvancedSettings = model.convertAdvancedSettingsToAPI(ctx) + 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 { @@ -804,7 +817,18 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat featur } // Handle advanced_settings - body.AdvancedSettings = model.convertAdvancedSettingsToAPI(ctx) + 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) { diff --git a/internal/fleet/agent_policy/resource.go b/internal/fleet/agent_policy/resource.go index f3c7c037b..3cda6999e 100644 --- a/internal/fleet/agent_policy/resource.go +++ b/internal/fleet/agent_policy/resource.go @@ -94,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, @@ -103,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/version_test.go b/internal/fleet/agent_policy/version_test.go index 1f876d3b4..bf6e62ab6 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.BoolNull(), + }) + + // 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" From a933cbcc75ee18e75eb126541c350db60751fc6c Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 14:29:51 +0100 Subject: [PATCH 07/24] Check import in acc test --- internal/fleet/agent_policy/acc_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/fleet/agent_policy/acc_test.go b/internal/fleet/agent_policy/acc_test.go index e55222c78..7966aaa46 100644 --- a/internal/fleet/agent_policy/acc_test.go +++ b/internal/fleet/agent_policy/acc_test.go @@ -705,7 +705,20 @@ func TestAccResourceAgentPolicyWithAdvancedSettings(t *testing.T) { resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.download_target_directory", "/tmp/elastic-agent"), ), }, - // Step 3: Remove settings + // 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), From 6da0434c92689abf691e8ee4bb5d836b632d9fb4 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 16:11:26 +0100 Subject: [PATCH 08/24] Add remove and set_to_default steps to acc tests, adjust schema defaults. Change MonitoringRuntimeExperimental type from Bool to String. --- internal/fleet/agent_policy/acc_test.go | 66 +++++++++++++++++++ internal/fleet/agent_policy/models.go | 49 ++------------ internal/fleet/agent_policy/models_test.go | 26 ++++---- internal/fleet/agent_policy/schema.go | 46 ++++++++++++- .../remove_advanced_monitoring/main.tf | 2 + .../set_to_defaults/main.tf | 23 +++++++ .../set_to_defaults/variables.tf | 11 ++++ .../remove_settings/main.tf | 2 + .../set_to_defaults/main.tf | 16 +++++ .../set_to_defaults/variables.tf | 4 ++ internal/fleet/agent_policy/version_test.go | 2 +- 11 files changed, 190 insertions(+), 57 deletions(-) create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/variables.tf create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/main.tf create mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/variables.tf diff --git a/internal/fleet/agent_policy/acc_test.go b/internal/fleet/agent_policy/acc_test.go index 7966aaa46..7c8476a3d 100644 --- a/internal/fleet/agent_policy/acc_test.go +++ b/internal/fleet/agent_policy/acc_test.go @@ -731,6 +731,30 @@ func TestAccResourceAgentPolicyWithAdvancedSettings(t *testing.T) { 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"), + resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.monitoring_runtime_experimental", ""), + ), + }, }, }) } @@ -803,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 0edab4f78..0577f00fe 100644 --- a/internal/fleet/agent_policy/models.go +++ b/internal/fleet/agent_policy/models.go @@ -61,7 +61,7 @@ type advancedSettingsModel struct { GoMaxProcs types.Int32 `tfsdk:"go_max_procs"` DownloadTimeout customtypes.Duration `tfsdk:"download_timeout"` DownloadTargetDirectory types.String `tfsdk:"download_target_directory"` - MonitoringRuntimeExperimental types.Bool `tfsdk:"monitoring_runtime_experimental"` + MonitoringRuntimeExperimental types.String `tfsdk:"monitoring_runtime_experimental"` } // Advanced Monitoring Options models @@ -1014,13 +1014,13 @@ func (model *agentPolicyModel) populateAdvancedSettingsFromAPI(ctx context.Conte // Monitoring runtime experimental if data.AdvancedSettings.AgentMonitoringRuntimeExperimental != nil { - if b, ok := data.AdvancedSettings.AgentMonitoringRuntimeExperimental.(bool); ok { - settings.MonitoringRuntimeExperimental = types.BoolValue(b) + if str, ok := data.AdvancedSettings.AgentMonitoringRuntimeExperimental.(string); ok { + settings.MonitoringRuntimeExperimental = types.StringValue(str) } else { - settings.MonitoringRuntimeExperimental = types.BoolNull() + settings.MonitoringRuntimeExperimental = types.StringNull() } } else { - settings.MonitoringRuntimeExperimental = types.BoolNull() + settings.MonitoringRuntimeExperimental = types.StringNull() } obj, diags := types.ObjectValueFrom(ctx, advancedSettingsAttrTypes(), settings) @@ -1100,7 +1100,7 @@ func (model *agentPolicyModel) convertAdvancedSettingsToAPI(ctx context.Context) result.AgentDownloadTargetDirectory = settings.DownloadTargetDirectory.ValueString() } if utils.IsKnown(settings.MonitoringRuntimeExperimental) { - result.AgentMonitoringRuntimeExperimental = settings.MonitoringRuntimeExperimental.ValueBool() + result.AgentMonitoringRuntimeExperimental = settings.MonitoringRuntimeExperimental.ValueString() } return result @@ -1133,17 +1133,6 @@ func (model *agentPolicyModel) convertHttpMonitoringEndpointToAPI(ctx context.Co 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()) @@ -1194,32 +1183,6 @@ func (model *agentPolicyModel) convertDiagnosticsToAPI(ctx context.Context) *dia 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) { diff --git a/internal/fleet/agent_policy/models_test.go b/internal/fleet/agent_policy/models_test.go index 46fa95abd..b90c33d54 100644 --- a/internal/fleet/agent_policy/models_test.go +++ b/internal/fleet/agent_policy/models_test.go @@ -177,7 +177,7 @@ func TestConvertAdvancedSettingsToAPI(t *testing.T) { GoMaxProcs: types.Int32Null(), DownloadTimeout: customtypes.NewDurationNull(), DownloadTargetDirectory: types.StringNull(), - MonitoringRuntimeExperimental: types.BoolNull(), + MonitoringRuntimeExperimental: types.StringNull(), }), wantNil: true, }, @@ -193,7 +193,7 @@ func TestConvertAdvancedSettingsToAPI(t *testing.T) { GoMaxProcs: types.Int32Null(), DownloadTimeout: customtypes.NewDurationNull(), DownloadTargetDirectory: types.StringNull(), - MonitoringRuntimeExperimental: types.BoolNull(), + MonitoringRuntimeExperimental: types.StringNull(), }), wantNil: false, checkResult: func(t *testing.T, result *advancedSettingsAPIResult) { @@ -213,7 +213,7 @@ func TestConvertAdvancedSettingsToAPI(t *testing.T) { GoMaxProcs: types.Int32Value(4), DownloadTimeout: customtypes.NewDurationNull(), DownloadTargetDirectory: types.StringNull(), - MonitoringRuntimeExperimental: types.BoolNull(), + MonitoringRuntimeExperimental: types.StringNull(), }), wantNil: false, checkResult: func(t *testing.T, result *advancedSettingsAPIResult) { @@ -232,7 +232,7 @@ func TestConvertAdvancedSettingsToAPI(t *testing.T) { GoMaxProcs: types.Int32Value(2), DownloadTimeout: customtypes.NewDurationValue("2h"), DownloadTargetDirectory: types.StringValue("/tmp/elastic"), - MonitoringRuntimeExperimental: types.BoolValue(false), + MonitoringRuntimeExperimental: types.StringValue(""), }), wantNil: false, checkResult: func(t *testing.T, result *advancedSettingsAPIResult) { @@ -245,7 +245,7 @@ func TestConvertAdvancedSettingsToAPI(t *testing.T) { assert.Equal(t, int32(2), result.AgentLimitsGoMaxProcs) assert.Equal(t, "2h", result.AgentDownloadTimeout) assert.Equal(t, "/tmp/elastic", result.AgentDownloadTargetDirectory) - assert.Equal(t, false, result.AgentMonitoringRuntimeExperimental) + assert.Equal(t, "", result.AgentMonitoringRuntimeExperimental) }, }, } @@ -308,7 +308,7 @@ func TestConvertHttpMonitoringEndpointToAPI(t *testing.T) { wantHttp: false, }, { - name: "default values returns nil (omit from payload)", + name: "default values are sent (allows reset to defaults)", amo: createAmoObject(createHttpEndpointObject(httpMonitoringEndpointModel{ Enabled: types.BoolValue(false), Host: types.StringValue("localhost"), @@ -316,7 +316,9 @@ func TestConvertHttpMonitoringEndpointToAPI(t *testing.T) { BufferEnabled: types.BoolValue(false), PprofEnabled: types.BoolValue(false), })), - wantHttp: false, + wantHttp: true, + wantPprof: true, + wantPprofValue: false, }, { name: "enabled http endpoint returns values", @@ -435,7 +437,7 @@ func TestConvertDiagnosticsToAPI(t *testing.T) { wantDiag: false, }, { - name: "default rate limits values returns nil (omit from payload)", + name: "default rate limits values are sent (allows reset to defaults)", amo: createAmoObject(createDiagnosticsObject( createRateLimitsObject(rateLimitsModel{ Interval: customtypes.NewDurationValue("1m"), @@ -443,10 +445,11 @@ func TestConvertDiagnosticsToAPI(t *testing.T) { }), types.ObjectNull(fileUploaderAttrTypes()), )), - wantDiag: false, + wantDiag: true, + wantRateLimits: true, }, { - name: "default uploader values returns nil (omit from payload)", + name: "default uploader values are sent (allows reset to defaults)", amo: createAmoObject(createDiagnosticsObject( types.ObjectNull(rateLimitsAttrTypes()), createFileUploaderObject(fileUploaderModel{ @@ -455,7 +458,8 @@ func TestConvertDiagnosticsToAPI(t *testing.T) { MaxRetries: types.Int32Value(10), }), )), - wantDiag: false, + wantDiag: true, + wantUploader: true, }, { name: "custom rate limits interval returns values", diff --git a/internal/fleet/agent_policy/schema.go b/internal/fleet/agent_policy/schema.go index abfb637cb..dff4ac621 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.", @@ -177,11 +189,15 @@ func getSchema() schema.Schema { 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"), }, @@ -190,17 +206,20 @@ func getSchema() schema.Schema { 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), }, @@ -209,17 +228,20 @@ func getSchema() schema.Schema { 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), }, @@ -229,16 +251,21 @@ func getSchema() schema.Schema { 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, }, - "monitoring_runtime_experimental": schema.BoolAttribute{ - Description: "Enable experimental runtime monitoring.", + "monitoring_runtime_experimental": schema.StringAttribute{ + Description: "Experimental runtime monitoring mode. Valid values: '' (empty string to disable), 'process', 'otel'.", Optional: true, Computed: true, + Default: stringdefault.StaticString(""), + Validators: []validator.String{ + stringvalidator.OneOf("", "process", "otel"), + }, }, }, }, @@ -246,11 +273,17 @@ func getSchema() schema.Schema { 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.", @@ -291,11 +324,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').", @@ -316,6 +355,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/set_to_defaults/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf new file mode 100644 index 000000000..43fd27bb4 --- /dev/null +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf @@ -0,0 +1,23 @@ +provider "elasticstack" { + elasticsearch {} + kibana {} +} + +resource "elasticstack_fleet_agent_policy" "test_policy" { + name = var.policy_name + namespace = "default" + 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 = {} + diagnostics = { + rate_limits = {} + file_uploader = {} + } + } +} + diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/variables.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/variables.tf new file mode 100644 index 000000000..82b3eb243 --- /dev/null +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/variables.tf @@ -0,0 +1,11 @@ +variable "policy_name" { + type = string + description = "Name for the agent policy" +} + +variable "skip_destroy" { + type = bool + description = "Whether to skip destruction of the policy" + default = false +} + diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf index 5bc9fee00..1e318d76f 100644 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf @@ -9,5 +9,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { 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/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/version_test.go b/internal/fleet/agent_policy/version_test.go index bf6e62ab6..f4840bbaa 100644 --- a/internal/fleet/agent_policy/version_test.go +++ b/internal/fleet/agent_policy/version_test.go @@ -323,7 +323,7 @@ func TestAdvancedSettingsVersionValidation(t *testing.T) { GoMaxProcs: types.Int32Value(4), DownloadTimeout: customtypes.NewDurationNull(), DownloadTargetDirectory: types.StringNull(), - MonitoringRuntimeExperimental: types.BoolNull(), + MonitoringRuntimeExperimental: types.StringNull(), }) // Test case where advanced_settings is not supported (older version) From b5e8292c20821393df7b8777ecaf4ed6c46fa5ab Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 16:16:15 +0100 Subject: [PATCH 09/24] fmt --- docs/resources/fleet_agent_policy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/fleet_agent_policy.md b/docs/resources/fleet_agent_policy.md index 50bc715ab..ac255526b 100644 --- a/docs/resources/fleet_agent_policy.md +++ b/docs/resources/fleet_agent_policy.md @@ -160,7 +160,7 @@ Optional: - `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` (Boolean) Enable experimental runtime monitoring. +- `monitoring_runtime_experimental` (String) Experimental runtime monitoring mode. Valid values: '' (empty string to disable), 'process', 'otel'. From ce9471ad85b00fd53b4ec7daa92bceb6c8f1315d Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 16:35:26 +0100 Subject: [PATCH 10/24] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df75b13e1..09dca376b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Added -- Add `advanced_settings` to `elasticstack_fleet_agent_policy` to configure agent logging, CPU limits, and download settings ([#1116](https://github.com/elastic/terraform-provider-elasticstack/issues/1116)) +- 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 From 3a37d92141302e729edc77d22368af003ace9f0b Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 17:01:55 +0100 Subject: [PATCH 11/24] fix space_id issue --- internal/fleet/agent_policy/schema.go | 4 ---- .../remove_advanced_monitoring/main.tf | 1 + .../set_to_defaults/main.tf | 1 + .../remove_settings/main.tf | 1 + .../set_to_defaults/main.tf | 1 + 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/fleet/agent_policy/schema.go b/internal/fleet/agent_policy/schema.go index dff4ac621..1776eab2d 100644 --- a/internal/fleet/agent_policy/schema.go +++ b/internal/fleet/agent_policy/schema.go @@ -20,7 +20,6 @@ import ( "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" @@ -167,9 +166,6 @@ 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.", 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 4fbbb521c..c40fdf0d7 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,6 +10,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { monitor_logs = true monitor_metrics = true skip_destroy = var.skip_destroy + space_ids = ["default"] # advanced_monitoring_options removed entirely - UseStateForUnknown preserves state } diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf index 43fd27bb4..ed7b2a4af 100644 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf @@ -10,6 +10,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { monitor_logs = true monitor_metrics = true skip_destroy = var.skip_destroy + space_ids = ["default"] # Empty nested blocks - schema defaults are applied for leaf attributes advanced_monitoring_options = { diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf index 1e318d76f..9a05e0473 100644 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf @@ -9,6 +9,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { description = "Test Agent Policy without Advanced Settings" monitor_logs = true monitor_metrics = true + space_ids = ["default"] # advanced_settings removed entirely - UseStateForUnknown preserves state } 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 index 4487bd99a..bcba61173 100644 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/main.tf +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/main.tf @@ -9,6 +9,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { description = "Test Agent Policy with Default Advanced Settings" monitor_logs = true monitor_metrics = true + space_ids = ["default"] # Empty block - schema defaults are applied for flat attributes advanced_settings = {} From ea440c7d732bde918d079cc9ce33e07efbe4ff2d Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 17:21:10 +0100 Subject: [PATCH 12/24] fix issue with monitoring_runtime_experimental --- internal/fleet/agent_policy/acc_test.go | 2 +- internal/fleet/agent_policy/schema.go | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/fleet/agent_policy/acc_test.go b/internal/fleet/agent_policy/acc_test.go index 7c8476a3d..2320e12fc 100644 --- a/internal/fleet/agent_policy/acc_test.go +++ b/internal/fleet/agent_policy/acc_test.go @@ -752,7 +752,7 @@ func TestAccResourceAgentPolicyWithAdvancedSettings(t *testing.T) { 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"), - resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "advanced_settings.monitoring_runtime_experimental", ""), + // monitoring_runtime_experimental is not checked - it's null when not set (no default, UseStateForUnknown) ), }, }, diff --git a/internal/fleet/agent_policy/schema.go b/internal/fleet/agent_policy/schema.go index 1776eab2d..0bfa4c96f 100644 --- a/internal/fleet/agent_policy/schema.go +++ b/internal/fleet/agent_policy/schema.go @@ -253,12 +253,17 @@ func getSchema() schema.Schema { 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, - Default: stringdefault.StaticString(""), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, Validators: []validator.String{ stringvalidator.OneOf("", "process", "otel"), }, From f077dd58f0390ec3b1e4f09d79b27622fc097550 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Fri, 12 Dec 2025 19:23:00 +0100 Subject: [PATCH 13/24] remove space_ids from new acc tests --- .../remove_advanced_monitoring/main.tf | 1 - 1 file changed, 1 deletion(-) 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 c40fdf0d7..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,7 +10,6 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { monitor_logs = true monitor_metrics = true skip_destroy = var.skip_destroy - space_ids = ["default"] # advanced_monitoring_options removed entirely - UseStateForUnknown preserves state } From e812fbdacc95f497db5614bb6b6aa7e2f23b64cb Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 12:32:11 +0100 Subject: [PATCH 14/24] fix space_ids issue --- internal/fleet/agent_policy/models.go | 2 +- internal/fleet/agent_policy/schema.go | 4 ++++ .../set_to_defaults/main.tf | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/fleet/agent_policy/models.go b/internal/fleet/agent_policy/models.go index 0577f00fe..b657af079 100644 --- a/internal/fleet/agent_policy/models.go +++ b/internal/fleet/agent_policy/models.go @@ -221,7 +221,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 diff --git a/internal/fleet/agent_policy/schema.go b/internal/fleet/agent_policy/schema.go index 0bfa4c96f..0194c32ad 100644 --- a/internal/fleet/agent_policy/schema.go +++ b/internal/fleet/agent_policy/schema.go @@ -20,6 +20,7 @@ import ( "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" @@ -166,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.", diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf index ed7b2a4af..43fd27bb4 100644 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/set_to_defaults/main.tf @@ -10,7 +10,6 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { monitor_logs = true monitor_metrics = true skip_destroy = var.skip_destroy - space_ids = ["default"] # Empty nested blocks - schema defaults are applied for leaf attributes advanced_monitoring_options = { From f9160cd276490c27274ec822758027fa8e3a3e97 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 12:50:03 +0100 Subject: [PATCH 15/24] Update golang --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index cd9de63d4..a9d4be962 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 From 734a88ab6651f446a97c8e93186a9fa068cb494c Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 13:08:14 +0100 Subject: [PATCH 16/24] Add missing field --- internal/fleet/agent_policy/models.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/fleet/agent_policy/models.go b/internal/fleet/agent_policy/models.go index b657af079..5913d2426 100644 --- a/internal/fleet/agent_policy/models.go +++ b/internal/fleet/agent_policy/models.go @@ -1035,6 +1035,7 @@ func (model *agentPolicyModel) populateAdvancedSettingsFromAPI(ctx context.Conte 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"` From 4d5c30df5a886b8bc748e5807c8972a0d09e835e Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 14:16:39 +0100 Subject: [PATCH 17/24] remove space_ids from config --- .../remove_settings/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf index 9a05e0473..1e318d76f 100644 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/remove_settings/main.tf @@ -9,7 +9,6 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { description = "Test Agent Policy without Advanced Settings" monitor_logs = true monitor_metrics = true - space_ids = ["default"] # advanced_settings removed entirely - UseStateForUnknown preserves state } From 904d8284e6cfba14b67ea7912705d188a0a14d25 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 14:41:04 +0100 Subject: [PATCH 18/24] remove space_ids from config --- .../set_to_defaults/main.tf | 1 - 1 file changed, 1 deletion(-) 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 index bcba61173..4487bd99a 100644 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/main.tf +++ b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedSettings/set_to_defaults/main.tf @@ -9,7 +9,6 @@ resource "elasticstack_fleet_agent_policy" "test_policy" { description = "Test Agent Policy with Default Advanced Settings" monitor_logs = true monitor_metrics = true - space_ids = ["default"] # Empty block - schema defaults are applied for flat attributes advanced_settings = {} From 3f884f87f0071217c862165c91cb9c9e5f05199f Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 14:42:25 +0100 Subject: [PATCH 19/24] remove obsolete test configs --- .../remove_diagnostics/main.tf | 24 ------------------- .../remove_diagnostics/variables.tf | 11 --------- 2 files changed, 35 deletions(-) delete mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/main.tf delete mode 100644 internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/variables.tf diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/main.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/main.tf deleted file mode 100644 index f2b046008..000000000 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/main.tf +++ /dev/null @@ -1,24 +0,0 @@ -provider "elasticstack" { - elasticsearch {} - kibana {} -} - -resource "elasticstack_fleet_agent_policy" "test_policy" { - name = var.policy_name - namespace = "default" - description = "Test Agent Policy - Diagnostics Removed" - monitor_logs = true - monitor_metrics = true - skip_destroy = var.skip_destroy - - advanced_monitoring_options = { - http_monitoring_endpoint = { - enabled = true - host = "0.0.0.0" - port = 8080 - buffer_enabled = true - pprof_enabled = true - } - } -} - diff --git a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/variables.tf b/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/variables.tf deleted file mode 100644 index 82b3eb243..000000000 --- a/internal/fleet/agent_policy/testdata/TestAccResourceAgentPolicyWithAdvancedMonitoring/remove_diagnostics/variables.tf +++ /dev/null @@ -1,11 +0,0 @@ -variable "policy_name" { - type = string - description = "Name for the agent policy" -} - -variable "skip_destroy" { - type = bool - description = "Whether to skip destruction of the policy" - default = false -} - From 82424f8b32e084afd5ae0b33e282acc4fa9ba9b1 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 15:34:55 +0100 Subject: [PATCH 20/24] Move advanced_settings and monitoring to dedicated files. --- internal/fleet/agent_policy/models.go | 536 ------------------ .../models_advanced_monitoring.go | 329 +++++++++++ .../models_advanced_monitoring_test.go | 264 +++++++++ .../agent_policy/models_advanced_settings.go | 236 ++++++++ .../models_advanced_settings_test.go | 136 +++++ internal/fleet/agent_policy/models_test.go | 380 ------------- 6 files changed, 965 insertions(+), 916 deletions(-) create mode 100644 internal/fleet/agent_policy/models_advanced_monitoring.go create mode 100644 internal/fleet/agent_policy/models_advanced_monitoring_test.go create mode 100644 internal/fleet/agent_policy/models_advanced_settings.go create mode 100644 internal/fleet/agent_policy/models_advanced_settings_test.go diff --git a/internal/fleet/agent_policy/models.go b/internal/fleet/agent_policy/models.go index 5913d2426..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 ( @@ -51,63 +49,6 @@ type globalDataTagsItemModel struct { NumberValue types.Float32 `tfsdk:"number_value"` } -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"` -} - -// 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"` @@ -263,162 +204,6 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi. 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() { - 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() -} - // convertGlobalDataTags converts the global data tags from terraform model to API model // and performs version validation func (model *agentPolicyModel) convertGlobalDataTags(ctx context.Context, feat features) (*[]kbapi.AgentPolicyGlobalDataTagsItem, diag.Diagnostics) { @@ -898,324 +683,3 @@ func mergeAgentFeature(existing []apiAgentFeature, newFeature *apiAgentFeature) return &result } - -// 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() -} - -// 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 -} - -// 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"` -} - -// 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 -} - -// 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.go b/internal/fleet/agent_policy/models_advanced_monitoring.go new file mode 100644 index 000000000..2b26d06e6 --- /dev/null +++ b/internal/fleet/agent_policy/models_advanced_monitoring.go @@ -0,0 +1,329 @@ +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..2b7892fd4 --- /dev/null +++ b/internal/fleet/agent_policy/models_advanced_monitoring_test.go @@ -0,0 +1,264 @@ +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..86eb9ef89 --- /dev/null +++ b/internal/fleet/agent_policy/models_advanced_settings.go @@ -0,0 +1,236 @@ +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..e1e3dc171 --- /dev/null +++ b/internal/fleet/agent_policy/models_advanced_settings_test.go @@ -0,0 +1,136 @@ +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 b90c33d54..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,381 +143,3 @@ func TestConvertHostNameFormatToAgentFeature(t *testing.T) { }) } } - -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) - } - }) - } -} - -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) - } - }) - } -} From 439e51165f47c1b441b56105b457bd1f32de623d Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 15:38:11 +0100 Subject: [PATCH 21/24] fmt --- internal/fleet/agent_policy/models_advanced_monitoring.go | 1 - internal/fleet/agent_policy/models_advanced_monitoring_test.go | 1 - internal/fleet/agent_policy/models_advanced_settings.go | 1 - internal/fleet/agent_policy/models_advanced_settings_test.go | 1 - 4 files changed, 4 deletions(-) diff --git a/internal/fleet/agent_policy/models_advanced_monitoring.go b/internal/fleet/agent_policy/models_advanced_monitoring.go index 2b26d06e6..6f41137e6 100644 --- a/internal/fleet/agent_policy/models_advanced_monitoring.go +++ b/internal/fleet/agent_policy/models_advanced_monitoring.go @@ -326,4 +326,3 @@ func (model *agentPolicyModel) convertDiagnosticsToAPI(ctx context.Context) *dia return result } - diff --git a/internal/fleet/agent_policy/models_advanced_monitoring_test.go b/internal/fleet/agent_policy/models_advanced_monitoring_test.go index 2b7892fd4..ca6003321 100644 --- a/internal/fleet/agent_policy/models_advanced_monitoring_test.go +++ b/internal/fleet/agent_policy/models_advanced_monitoring_test.go @@ -261,4 +261,3 @@ func TestConvertDiagnosticsToAPI(t *testing.T) { }) } } - diff --git a/internal/fleet/agent_policy/models_advanced_settings.go b/internal/fleet/agent_policy/models_advanced_settings.go index 86eb9ef89..8e15a4b08 100644 --- a/internal/fleet/agent_policy/models_advanced_settings.go +++ b/internal/fleet/agent_policy/models_advanced_settings.go @@ -233,4 +233,3 @@ func (model *agentPolicyModel) convertAdvancedSettingsToAPI(ctx context.Context) return result } - diff --git a/internal/fleet/agent_policy/models_advanced_settings_test.go b/internal/fleet/agent_policy/models_advanced_settings_test.go index e1e3dc171..8c97e90da 100644 --- a/internal/fleet/agent_policy/models_advanced_settings_test.go +++ b/internal/fleet/agent_policy/models_advanced_settings_test.go @@ -133,4 +133,3 @@ func TestConvertAdvancedSettingsToAPI(t *testing.T) { }) } } - From accc9bc2a6aab86df1a4054e7d74062ef85d8ae0 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 15:39:39 +0100 Subject: [PATCH 22/24] Add setup-synthetics target to Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) 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)"}' From c489dca0e782a120e09da82cd866236ad03adec5 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 15:40:09 +0100 Subject: [PATCH 23/24] Update stack versions in CI tests --- .github/workflows/test.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 747672a3c..65352bc92 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -128,12 +128,13 @@ 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' + - '9.2.2' # The default runner image can be overridden for specific versions if needed # include: # - version: '8.0.1' From 595153bfeeb587d8aa331423f3b3fdc0075a38f0 Mon Sep 17 00:00:00 2001 From: Dmitry Onishchenko Date: Mon, 15 Dec 2025 16:35:57 +0100 Subject: [PATCH 24/24] Temp remove 9.2.2 --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 65352bc92..77bdff3aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -134,7 +134,6 @@ jobs: - '8.19.8' - '9.0.8' - '9.1.8' - - '9.2.2' # The default runner image can be overridden for specific versions if needed # include: # - version: '8.0.1'