Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Allow version changes without a destroy/create cycle with `elasticstack_fleet_integration` ([#1255](https://github.com/elastic/terraform-provider-elasticstack/pull/1255)). This fixes an issue where it was impossible to upgrade integrations which are used by an integration policy.
- Add `namespace` attribute to `elasticstack_kibana_synthetics_monitor` resource to support setting data stream namespace independently from `space_id` ([#1247](https://github.com/elastic/terraform-provider-elasticstack/pull/1247))
- Migrate `elasticstack_elasticsearch_security_role_mapping` resource and data source to Terraform Plugin Framework ([#1279](https://github.com/elastic/terraform-provider-elasticstack/pull/1279))
- Add support for `inactivity_timeout` in `elasticstack_fleet_agent_policy` ([#641](https://github.com/elastic/terraform-provider-elasticstack/issues/641))

## [0.11.17] - 2025-07-21

Expand Down
1 change: 1 addition & 0 deletions docs/resources/fleet_agent_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" {
- `download_source_id` (String) The identifier for the Elastic Agent binary download server.
- `fleet_server_host_id` (String) The identifier for the Fleet server host.
- `global_data_tags` (Attributes Map) 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} (see [below for nested schema](#nestedatt--global_data_tags))
- `inactivity_timeout` (String) The inactivity timeout for the agent policy. If an agent does not report within this time period, it will be considered inactive. Supports duration strings (e.g., '30s', '2m', '1h').
- `monitor_logs` (Boolean) Enable collection of agent logs.
- `monitor_metrics` (Boolean) Enable collection of agent metrics.
- `monitoring_output_id` (String) The identifier for monitoring output.
Expand Down
125 changes: 88 additions & 37 deletions internal/fleet/agent_policy/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"context"
"fmt"
"slices"
"time"

"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"
Expand All @@ -17,6 +19,7 @@ import (
type features struct {
SupportsGlobalDataTags bool
SupportsSupportsAgentless bool
SupportsInactivityTimeout bool
}

type globalDataTagsItemModel struct {
Expand All @@ -25,21 +28,22 @@ type globalDataTagsItemModel struct {
}

type agentPolicyModel struct {
ID types.String `tfsdk:"id"`
PolicyID types.String `tfsdk:"policy_id"`
Name types.String `tfsdk:"name"`
Namespace types.String `tfsdk:"namespace"`
Description types.String `tfsdk:"description"`
DataOutputId types.String `tfsdk:"data_output_id"`
MonitoringOutputId types.String `tfsdk:"monitoring_output_id"`
FleetServerHostId types.String `tfsdk:"fleet_server_host_id"`
DownloadSourceId types.String `tfsdk:"download_source_id"`
MonitorLogs types.Bool `tfsdk:"monitor_logs"`
MonitorMetrics types.Bool `tfsdk:"monitor_metrics"`
SysMonitoring types.Bool `tfsdk:"sys_monitoring"`
SkipDestroy types.Bool `tfsdk:"skip_destroy"`
SupportsAgentless types.Bool `tfsdk:"supports_agentless"`
GlobalDataTags types.Map `tfsdk:"global_data_tags"` //> globalDataTagsModel
ID types.String `tfsdk:"id"`
PolicyID types.String `tfsdk:"policy_id"`
Name types.String `tfsdk:"name"`
Namespace types.String `tfsdk:"namespace"`
Description types.String `tfsdk:"description"`
DataOutputId types.String `tfsdk:"data_output_id"`
MonitoringOutputId types.String `tfsdk:"monitoring_output_id"`
FleetServerHostId types.String `tfsdk:"fleet_server_host_id"`
DownloadSourceId types.String `tfsdk:"download_source_id"`
MonitorLogs types.Bool `tfsdk:"monitor_logs"`
MonitorMetrics types.Bool `tfsdk:"monitor_metrics"`
SysMonitoring types.Bool `tfsdk:"sys_monitoring"`
SkipDestroy types.Bool `tfsdk:"skip_destroy"`
SupportsAgentless types.Bool `tfsdk:"supports_agentless"`
InactivityTimeout customtypes.Duration `tfsdk:"inactivity_timeout"`
GlobalDataTags types.Map `tfsdk:"global_data_tags"` //> globalDataTagsModel
}

func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.AgentPolicy) diag.Diagnostics {
Expand Down Expand Up @@ -73,6 +77,13 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.
model.Name = types.StringValue(data.Name)
model.Namespace = types.StringValue(data.Namespace)
model.SupportsAgentless = types.BoolPointerValue(data.SupportsAgentless)
if data.InactivityTimeout != nil {
// Convert seconds to duration string
d := time.Duration(*data.InactivityTimeout * float32(time.Second)).Truncate(time.Second)
model.InactivityTimeout = customtypes.NewDurationValue(d.String())
} else {
model.InactivityTimeout = customtypes.NewDurationNull()
}
if utils.Deref(data.GlobalDataTags) != nil {
diags := diag.Diagnostics{}
var map0 = make(map[string]globalDataTagsItemModel)
Expand Down Expand Up @@ -162,16 +173,6 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat featur
monitoring = append(monitoring, kbapi.PostFleetAgentPoliciesJSONBodyMonitoringEnabledMetrics)
}

if utils.IsKnown(model.SupportsAgentless) && !feat.SupportsSupportsAgentless {
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("supports_agentless"),
"Unsupported Elasticsearch version",
fmt.Sprintf("Supports agentless is only supported in Elastic Stack %s and above", MinSupportsAgentlessVersion),
),
}
}

body := kbapi.PostFleetAgentPoliciesJSONRequestBody{
DataOutputId: model.DataOutputId.ValueStringPointer(),
Description: model.Description.ValueStringPointer(),
Expand All @@ -182,7 +183,37 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat featur
MonitoringOutputId: model.MonitoringOutputId.ValueStringPointer(),
Name: model.Name.ValueString(),
Namespace: model.Namespace.ValueString(),
SupportsAgentless: model.SupportsAgentless.ValueBoolPointer(),
}

if utils.IsKnown(model.SupportsAgentless) {
if !feat.SupportsSupportsAgentless {
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("supports_agentless"),
"Unsupported Elasticsearch version",
fmt.Sprintf("Supports agentless is only supported in Elastic Stack %s and above", MinSupportsAgentlessVersion),
),
}
}
body.SupportsAgentless = model.SupportsAgentless.ValueBoolPointer()
}

if utils.IsKnown(model.InactivityTimeout) {
if !feat.SupportsInactivityTimeout {
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("inactivity_timeout"),
"Unsupported Elasticsearch version",
fmt.Sprintf("Inactivity timeout is only supported in Elastic Stack %s and above", MinVersionInactivityTimeout),
),
}
}
duration, diags := model.InactivityTimeout.Parse()
if diags.HasError() {
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diags
}
seconds := float32(duration.Seconds())
body.InactivityTimeout = &seconds
}

tags, diags := model.convertGlobalDataTags(ctx, feat)
Expand All @@ -203,16 +234,6 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat featur
monitoring = append(monitoring, kbapi.Metrics)
}

if utils.IsKnown(model.SupportsAgentless) && !feat.SupportsSupportsAgentless {
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("supports_agentless"),
"Unsupported Elasticsearch version",
fmt.Sprintf("Supports agentless is only supported in Elastic Stack %s and above", MinSupportsAgentlessVersion),
),
}
}

body := kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{
DataOutputId: model.DataOutputId.ValueStringPointer(),
Description: model.Description.ValueStringPointer(),
Expand All @@ -222,7 +243,37 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat featur
MonitoringOutputId: model.MonitoringOutputId.ValueStringPointer(),
Name: model.Name.ValueString(),
Namespace: model.Namespace.ValueString(),
SupportsAgentless: model.SupportsAgentless.ValueBoolPointer(),
}

if utils.IsKnown(model.SupportsAgentless) {
if !feat.SupportsSupportsAgentless {
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("supports_agentless"),
"Unsupported Elasticsearch version",
fmt.Sprintf("Supports agentless is only supported in Elastic Stack %s and above", MinSupportsAgentlessVersion),
),
}
}
body.SupportsAgentless = model.SupportsAgentless.ValueBoolPointer()
}

if utils.IsKnown(model.InactivityTimeout) {
if !feat.SupportsInactivityTimeout {
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("inactivity_timeout"),
"Unsupported Elasticsearch version",
fmt.Sprintf("Inactivity timeout is only supported in Elastic Stack %s and above", MinVersionInactivityTimeout),
),
}
}
duration, diags := model.InactivityTimeout.Parse()
if diags.HasError() {
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diags
}
seconds := float32(duration.Seconds())
body.InactivityTimeout = &seconds
}

tags, diags := model.convertGlobalDataTags(ctx, feat)
Expand Down
7 changes: 7 additions & 0 deletions internal/fleet/agent_policy/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var (
var (
MinVersionGlobalDataTags = version.Must(version.NewVersion("8.15.0"))
MinSupportsAgentlessVersion = version.Must(version.NewVersion("8.15.0"))
MinVersionInactivityTimeout = version.Must(version.NewVersion("8.7.0"))
)

// NewResource is a helper function to simplify the provider implementation.
Expand Down Expand Up @@ -57,8 +58,14 @@ func (r *agentPolicyResource) buildFeatures(ctx context.Context) (features, diag
return features{}, utils.FrameworkDiagsFromSDK(diags)
}

supportsInactivityTimeout, diags := r.client.EnforceMinVersion(ctx, MinVersionInactivityTimeout)
if diags.HasError() {
return features{}, utils.FrameworkDiagsFromSDK(diags)
}

return features{
SupportsGlobalDataTags: supportsGDT,
SupportsSupportsAgentless: supportsSupportsAgentless,
SupportsInactivityTimeout: supportsInactivityTimeout,
}, nil
}
37 changes: 37 additions & 0 deletions internal/fleet/agent_policy/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@ func TestAccResourceAgentPolicy(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"skip_destroy"},
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionInactivityTimeout),
Config: testAccResourceAgentPolicyCreateWithInactivityTimeout(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 Inactivity Timeout"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "monitor_logs", "true"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "monitor_metrics", "false"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "skip_destroy", "false"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "inactivity_timeout", "2m"),
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionGlobalDataTags),
Config: testAccResourceAgentPolicyCreateWithGlobalDataTags(policyNameGlobalDataTags, false),
Expand Down Expand Up @@ -295,6 +308,30 @@ data "elasticstack_fleet_enrollment_tokens" "test_policy" {
`, fmt.Sprintf("Policy %s", id), skipDestroy)
}

func testAccResourceAgentPolicyCreateWithInactivityTimeout(id string) string {
return fmt.Sprintf(`
provider "elasticstack" {
elasticsearch {}
kibana {}
}

resource "elasticstack_fleet_agent_policy" "test_policy" {
name = "%s"
namespace = "default"
description = "Test Agent Policy with Inactivity Timeout"
monitor_logs = true
monitor_metrics = false
skip_destroy = false
inactivity_timeout = "2m"
}

data "elasticstack_fleet_enrollment_tokens" "test_policy" {
policy_id = elasticstack_fleet_agent_policy.test_policy.policy_id
}

`, fmt.Sprintf("Policy %s", id))
}

func testAccResourceAgentPolicyCreateWithBadGlobalDataTags(id string, skipDestroy bool) string {
return fmt.Sprintf(`
provider "elasticstack" {
Expand Down
7 changes: 7 additions & 0 deletions internal/fleet/agent_policy/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package agent_policy
import (
"context"

"github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
"github.com/hashicorp/terraform-plugin-framework-validators/float32validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
Expand Down Expand Up @@ -97,6 +98,12 @@ func getSchema() schema.Schema {
boolplanmodifier.RequiresReplace(),
},
},
"inactivity_timeout": schema.StringAttribute{
Description: "The inactivity timeout for the agent policy. If an agent does not report within this time period, it will be considered inactive. Supports duration strings (e.g., '30s', '2m', '1h').",
Computed: true,
Optional: true,
CustomType: customtypes.DurationType{},
},
"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}",
NestedObject: schema.NestedAttributeObject{
Expand Down
Loading
Loading