Skip to content

Commit 8d86de6

Browse files
Copilottobio
andauthored
Add inactivity_timeout support to Fleet agent policy resource with duration string type and version validation (#1285)
* Initial plan * Add inactivity_timeout support to Fleet agent policy resource Co-authored-by: tobio <[email protected]> * Update changelog and generate documentation for inactivity_timeout feature Co-authored-by: tobio <[email protected]> * Add version check for inactivity_timeout field in Fleet agent policy - Added MinVersionInactivityTimeout constant for 8.7.0 - Added SupportsInactivityTimeout field to features struct - Updated buildFeatures to check for inactivity timeout support - Added version validation in toAPICreateModel and toAPIUpdateModel - Updated test to use proper version check for inactivity_timeout Co-authored-by: tobio <[email protected]> * Add comprehensive tests for inactivity_timeout version validation - Added unit tests to verify MinVersionInactivityTimeout constant - Added comprehensive validation tests for version checking logic - Verified version validation works for both create and update operations - Ensured null/unset inactivity_timeout values don't trigger validation errors Co-authored-by: tobio <[email protected]> * Fix inactivity_timeout and supports_agentless field handling in Fleet agent policy Co-authored-by: tobio <[email protected]> * Convert inactivity_timeout to string duration type with seconds conversion Co-authored-by: tobio <[email protected]> * Update documentation for inactivity_timeout duration string support Co-authored-by: tobio <[email protected]> * Apply code review suggestions: improve duration conversion and simplify test helpers Co-authored-by: tobio <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: tobio <[email protected]>
1 parent f5a7da3 commit 8d86de6

File tree

7 files changed

+247
-37
lines changed

7 files changed

+247
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Support setting an explit `connector_id` in `elasticstack_kibana_action_connector`. This attribute already existed, but was being ignored by the provider. Setting the attribute will return an error in Elastic Stack v8.8 and lower since creating a connector with an explicit ID is not supported. ([1260](https://github.com/elastic/terraform-provider-elasticstack/pull/1260))
1313
- Migrate `elasticstack_kibana_action_connector` to the Terraform plugin framework ([#1269](https://github.com/elastic/terraform-provider-elasticstack/pull/1269))
1414
- Migrate `elasticstack_elasticsearch_security_role_mapping` resource and data source to Terraform Plugin Framework ([#1279](https://github.com/elastic/terraform-provider-elasticstack/pull/1279))
15+
- Add support for `inactivity_timeout` in `elasticstack_fleet_agent_policy` ([#641](https://github.com/elastic/terraform-provider-elasticstack/issues/641))
1516

1617
## [0.11.17] - 2025-07-21
1718

docs/resources/fleet_agent_policy.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" {
5151
- `download_source_id` (String) The identifier for the Elastic Agent binary download server.
5252
- `fleet_server_host_id` (String) The identifier for the Fleet server host.
5353
- `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))
54+
- `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').
5455
- `monitor_logs` (Boolean) Enable collection of agent logs.
5556
- `monitor_metrics` (Boolean) Enable collection of agent metrics.
5657
- `monitoring_output_id` (String) The identifier for monitoring output.

internal/fleet/agent_policy/models.go

Lines changed: 88 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"context"
55
"fmt"
66
"slices"
7+
"time"
78

89
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
910
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
11+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
1012

1113
"github.com/hashicorp/terraform-plugin-framework/attr"
1214
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -17,6 +19,7 @@ import (
1719
type features struct {
1820
SupportsGlobalDataTags bool
1921
SupportsSupportsAgentless bool
22+
SupportsInactivityTimeout bool
2023
}
2124

2225
type globalDataTagsItemModel struct {
@@ -25,21 +28,22 @@ type globalDataTagsItemModel struct {
2528
}
2629

2730
type agentPolicyModel struct {
28-
ID types.String `tfsdk:"id"`
29-
PolicyID types.String `tfsdk:"policy_id"`
30-
Name types.String `tfsdk:"name"`
31-
Namespace types.String `tfsdk:"namespace"`
32-
Description types.String `tfsdk:"description"`
33-
DataOutputId types.String `tfsdk:"data_output_id"`
34-
MonitoringOutputId types.String `tfsdk:"monitoring_output_id"`
35-
FleetServerHostId types.String `tfsdk:"fleet_server_host_id"`
36-
DownloadSourceId types.String `tfsdk:"download_source_id"`
37-
MonitorLogs types.Bool `tfsdk:"monitor_logs"`
38-
MonitorMetrics types.Bool `tfsdk:"monitor_metrics"`
39-
SysMonitoring types.Bool `tfsdk:"sys_monitoring"`
40-
SkipDestroy types.Bool `tfsdk:"skip_destroy"`
41-
SupportsAgentless types.Bool `tfsdk:"supports_agentless"`
42-
GlobalDataTags types.Map `tfsdk:"global_data_tags"` //> globalDataTagsModel
31+
ID types.String `tfsdk:"id"`
32+
PolicyID types.String `tfsdk:"policy_id"`
33+
Name types.String `tfsdk:"name"`
34+
Namespace types.String `tfsdk:"namespace"`
35+
Description types.String `tfsdk:"description"`
36+
DataOutputId types.String `tfsdk:"data_output_id"`
37+
MonitoringOutputId types.String `tfsdk:"monitoring_output_id"`
38+
FleetServerHostId types.String `tfsdk:"fleet_server_host_id"`
39+
DownloadSourceId types.String `tfsdk:"download_source_id"`
40+
MonitorLogs types.Bool `tfsdk:"monitor_logs"`
41+
MonitorMetrics types.Bool `tfsdk:"monitor_metrics"`
42+
SysMonitoring types.Bool `tfsdk:"sys_monitoring"`
43+
SkipDestroy types.Bool `tfsdk:"skip_destroy"`
44+
SupportsAgentless types.Bool `tfsdk:"supports_agentless"`
45+
InactivityTimeout customtypes.Duration `tfsdk:"inactivity_timeout"`
46+
GlobalDataTags types.Map `tfsdk:"global_data_tags"` //> globalDataTagsModel
4347
}
4448

4549
func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.AgentPolicy) diag.Diagnostics {
@@ -73,6 +77,13 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.
7377
model.Name = types.StringValue(data.Name)
7478
model.Namespace = types.StringValue(data.Namespace)
7579
model.SupportsAgentless = types.BoolPointerValue(data.SupportsAgentless)
80+
if data.InactivityTimeout != nil {
81+
// Convert seconds to duration string
82+
d := time.Duration(*data.InactivityTimeout * float32(time.Second)).Truncate(time.Second)
83+
model.InactivityTimeout = customtypes.NewDurationValue(d.String())
84+
} else {
85+
model.InactivityTimeout = customtypes.NewDurationNull()
86+
}
7687
if utils.Deref(data.GlobalDataTags) != nil {
7788
diags := diag.Diagnostics{}
7889
var map0 = make(map[string]globalDataTagsItemModel)
@@ -162,16 +173,6 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat featur
162173
monitoring = append(monitoring, kbapi.PostFleetAgentPoliciesJSONBodyMonitoringEnabledMetrics)
163174
}
164175

165-
if utils.IsKnown(model.SupportsAgentless) && !feat.SupportsSupportsAgentless {
166-
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diag.Diagnostics{
167-
diag.NewAttributeErrorDiagnostic(
168-
path.Root("supports_agentless"),
169-
"Unsupported Elasticsearch version",
170-
fmt.Sprintf("Supports agentless is only supported in Elastic Stack %s and above", MinSupportsAgentlessVersion),
171-
),
172-
}
173-
}
174-
175176
body := kbapi.PostFleetAgentPoliciesJSONRequestBody{
176177
DataOutputId: model.DataOutputId.ValueStringPointer(),
177178
Description: model.Description.ValueStringPointer(),
@@ -182,7 +183,37 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat featur
182183
MonitoringOutputId: model.MonitoringOutputId.ValueStringPointer(),
183184
Name: model.Name.ValueString(),
184185
Namespace: model.Namespace.ValueString(),
185-
SupportsAgentless: model.SupportsAgentless.ValueBoolPointer(),
186+
}
187+
188+
if utils.IsKnown(model.SupportsAgentless) {
189+
if !feat.SupportsSupportsAgentless {
190+
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diag.Diagnostics{
191+
diag.NewAttributeErrorDiagnostic(
192+
path.Root("supports_agentless"),
193+
"Unsupported Elasticsearch version",
194+
fmt.Sprintf("Supports agentless is only supported in Elastic Stack %s and above", MinSupportsAgentlessVersion),
195+
),
196+
}
197+
}
198+
body.SupportsAgentless = model.SupportsAgentless.ValueBoolPointer()
199+
}
200+
201+
if utils.IsKnown(model.InactivityTimeout) {
202+
if !feat.SupportsInactivityTimeout {
203+
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diag.Diagnostics{
204+
diag.NewAttributeErrorDiagnostic(
205+
path.Root("inactivity_timeout"),
206+
"Unsupported Elasticsearch version",
207+
fmt.Sprintf("Inactivity timeout is only supported in Elastic Stack %s and above", MinVersionInactivityTimeout),
208+
),
209+
}
210+
}
211+
duration, diags := model.InactivityTimeout.Parse()
212+
if diags.HasError() {
213+
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diags
214+
}
215+
seconds := float32(duration.Seconds())
216+
body.InactivityTimeout = &seconds
186217
}
187218

188219
tags, diags := model.convertGlobalDataTags(ctx, feat)
@@ -203,16 +234,6 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat featur
203234
monitoring = append(monitoring, kbapi.PutFleetAgentPoliciesAgentpolicyidJSONBodyMonitoringEnabledMetrics)
204235
}
205236

206-
if utils.IsKnown(model.SupportsAgentless) && !feat.SupportsSupportsAgentless {
207-
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diag.Diagnostics{
208-
diag.NewAttributeErrorDiagnostic(
209-
path.Root("supports_agentless"),
210-
"Unsupported Elasticsearch version",
211-
fmt.Sprintf("Supports agentless is only supported in Elastic Stack %s and above", MinSupportsAgentlessVersion),
212-
),
213-
}
214-
}
215-
216237
body := kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{
217238
DataOutputId: model.DataOutputId.ValueStringPointer(),
218239
Description: model.Description.ValueStringPointer(),
@@ -222,7 +243,37 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat featur
222243
MonitoringOutputId: model.MonitoringOutputId.ValueStringPointer(),
223244
Name: model.Name.ValueString(),
224245
Namespace: model.Namespace.ValueString(),
225-
SupportsAgentless: model.SupportsAgentless.ValueBoolPointer(),
246+
}
247+
248+
if utils.IsKnown(model.SupportsAgentless) {
249+
if !feat.SupportsSupportsAgentless {
250+
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diag.Diagnostics{
251+
diag.NewAttributeErrorDiagnostic(
252+
path.Root("supports_agentless"),
253+
"Unsupported Elasticsearch version",
254+
fmt.Sprintf("Supports agentless is only supported in Elastic Stack %s and above", MinSupportsAgentlessVersion),
255+
),
256+
}
257+
}
258+
body.SupportsAgentless = model.SupportsAgentless.ValueBoolPointer()
259+
}
260+
261+
if utils.IsKnown(model.InactivityTimeout) {
262+
if !feat.SupportsInactivityTimeout {
263+
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diag.Diagnostics{
264+
diag.NewAttributeErrorDiagnostic(
265+
path.Root("inactivity_timeout"),
266+
"Unsupported Elasticsearch version",
267+
fmt.Sprintf("Inactivity timeout is only supported in Elastic Stack %s and above", MinVersionInactivityTimeout),
268+
),
269+
}
270+
}
271+
duration, diags := model.InactivityTimeout.Parse()
272+
if diags.HasError() {
273+
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diags
274+
}
275+
seconds := float32(duration.Seconds())
276+
body.InactivityTimeout = &seconds
226277
}
227278

228279
tags, diags := model.convertGlobalDataTags(ctx, feat)

internal/fleet/agent_policy/resource.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var (
2121
var (
2222
MinVersionGlobalDataTags = version.Must(version.NewVersion("8.15.0"))
2323
MinSupportsAgentlessVersion = version.Must(version.NewVersion("8.15.0"))
24+
MinVersionInactivityTimeout = version.Must(version.NewVersion("8.7.0"))
2425
)
2526

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

61+
supportsInactivityTimeout, diags := r.client.EnforceMinVersion(ctx, MinVersionInactivityTimeout)
62+
if diags.HasError() {
63+
return features{}, utils.FrameworkDiagsFromSDK(diags)
64+
}
65+
6066
return features{
6167
SupportsGlobalDataTags: supportsGDT,
6268
SupportsSupportsAgentless: supportsSupportsAgentless,
69+
SupportsInactivityTimeout: supportsInactivityTimeout,
6370
}, nil
6471
}

internal/fleet/agent_policy/resource_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,19 @@ func TestAccResourceAgentPolicy(t *testing.T) {
143143
ImportStateVerify: true,
144144
ImportStateVerifyIgnore: []string{"skip_destroy"},
145145
},
146+
{
147+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionInactivityTimeout),
148+
Config: testAccResourceAgentPolicyCreateWithInactivityTimeout(policyName),
149+
Check: resource.ComposeTestCheckFunc(
150+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
151+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
152+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "Test Agent Policy with Inactivity Timeout"),
153+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "monitor_logs", "true"),
154+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "monitor_metrics", "false"),
155+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "skip_destroy", "false"),
156+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "inactivity_timeout", "2m"),
157+
),
158+
},
146159
{
147160
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionGlobalDataTags),
148161
Config: testAccResourceAgentPolicyCreateWithGlobalDataTags(policyNameGlobalDataTags, false),
@@ -295,6 +308,30 @@ data "elasticstack_fleet_enrollment_tokens" "test_policy" {
295308
`, fmt.Sprintf("Policy %s", id), skipDestroy)
296309
}
297310

311+
func testAccResourceAgentPolicyCreateWithInactivityTimeout(id string) string {
312+
return fmt.Sprintf(`
313+
provider "elasticstack" {
314+
elasticsearch {}
315+
kibana {}
316+
}
317+
318+
resource "elasticstack_fleet_agent_policy" "test_policy" {
319+
name = "%s"
320+
namespace = "default"
321+
description = "Test Agent Policy with Inactivity Timeout"
322+
monitor_logs = true
323+
monitor_metrics = false
324+
skip_destroy = false
325+
inactivity_timeout = "2m"
326+
}
327+
328+
data "elasticstack_fleet_enrollment_tokens" "test_policy" {
329+
policy_id = elasticstack_fleet_agent_policy.test_policy.policy_id
330+
}
331+
332+
`, fmt.Sprintf("Policy %s", id))
333+
}
334+
298335
func testAccResourceAgentPolicyCreateWithBadGlobalDataTags(id string, skipDestroy bool) string {
299336
return fmt.Sprintf(`
300337
provider "elasticstack" {

internal/fleet/agent_policy/schema.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package agent_policy
33
import (
44
"context"
55

6+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
67
"github.com/hashicorp/terraform-plugin-framework-validators/float32validator"
78
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
89
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -97,6 +98,12 @@ func getSchema() schema.Schema {
9798
boolplanmodifier.RequiresReplace(),
9899
},
99100
},
101+
"inactivity_timeout": schema.StringAttribute{
102+
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').",
103+
Computed: true,
104+
Optional: true,
105+
CustomType: customtypes.DurationType{},
106+
},
100107
"global_data_tags": schema.MapNestedAttribute{
101108
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}",
102109
NestedObject: schema.NestedAttributeObject{

0 commit comments

Comments
 (0)