Skip to content

Commit ab34617

Browse files
dimuontobio
andauthored
Fleet agent policy host name format field (#1521)
* Move rerun_fails to env var * Support host name format in fleet agent policy. * fmt * update docs * Update changelog * Enforce version * Update internal/fleet/agent_policy/schema.go Co-authored-by: Toby Brain <tobio85@gmail.com> * Update internal/fleet/agent_policy/schema.go Co-authored-by: Toby Brain <tobio85@gmail.com> * Don't fail for 8.6.2 if host_name_format is hostname --------- Co-authored-by: Toby Brain <tobio85@gmail.com>
1 parent 06a217e commit ab34617

File tree

17 files changed

+564
-33
lines changed

17 files changed

+564
-33
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ alias = [
4040

4141
### Changes
4242

43+
- Add `host_name_format` to `elasticstack_fleet_agent_policy` to configure host name format (hostname or FQDN) ([#1312](https://github.com/elastic/terraform-provider-elasticstack/pull/1312))
4344
- Create `elasticstack_kibana_prebuilt_rule` resource ([#1296](https://github.com/elastic/terraform-provider-elasticstack/pull/1296))
4445
- Add `required_versions` to `elasticstack_fleet_agent_policy` ([#1436](https://github.com/elastic/terraform-provider-elasticstack/pull/1436))
4546
- Migrate `elasticstack_elasticsearch_security_role` resource to Terraform Plugin Framework ([#1330](https://github.com/elastic/terraform-provider-elasticstack/pull/1330))

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ KIBANA_API_KEY_NAME ?= kibana-api-key
2828
FLEET_NAME ?= terraform-elasticstack-fleet
2929
FLEET_ENDPOINT ?= https://$(FLEET_NAME):8220
3030

31+
RERUN_FAILS ?= 3
32+
3133
export GOBIN = $(shell pwd)/bin
3234

3335

@@ -53,7 +55,7 @@ testacc-vs-docker:
5355

5456
.PHONY: testacc
5557
testacc: ## Run acceptance tests
56-
TF_ACC=1 go tool gotestsum --format testname --rerun-fails=3 --packages="-v ./..." -- -count $(ACCTEST_COUNT) -parallel $(ACCTEST_PARALLELISM) $(TESTARGS) -timeout $(ACCTEST_TIMEOUT)
58+
TF_ACC=1 go tool gotestsum --format testname --rerun-fails=$(RERUN_FAILS) --packages="-v ./..." -- -count $(ACCTEST_COUNT) -parallel $(ACCTEST_PARALLELISM) $(TESTARGS) -timeout $(ACCTEST_TIMEOUT)
5759

5860
.PHONY: test
5961
test: ## Run unit tests

docs/resources/fleet_agent_policy.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ provider "elasticstack" {
1818
}
1919
2020
resource "elasticstack_fleet_agent_policy" "test_policy" {
21-
name = "Test Policy"
22-
namespace = "default"
23-
description = "Test Agent Policy"
24-
sys_monitoring = true
25-
monitor_logs = true
26-
monitor_metrics = true
27-
space_ids = ["default"]
21+
name = "Test Policy"
22+
namespace = "default"
23+
description = "Test Agent Policy"
24+
sys_monitoring = true
25+
monitor_logs = true
26+
monitor_metrics = true
27+
space_ids = ["default"]
28+
host_name_format = "hostname" # or "fqdn" for fully qualified domain names
2829
2930
global_data_tags = {
3031
first_tag = {
@@ -52,6 +53,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" {
5253
- `download_source_id` (String) The identifier for the Elastic Agent binary download server.
5354
- `fleet_server_host_id` (String) The identifier for the Fleet server host.
5455
- `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))
56+
- `host_name_format` (String) Determines the format of the host.name field in events. Can be 'hostname' (short hostname, e.g., 'myhost') or 'fqdn' (fully qualified domain name, e.g., 'myhost.example.com'). Defaults to 'hostname'.
5557
- `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').
5658
- `monitor_logs` (Boolean) Enable collection of agent logs.
5759
- `monitor_metrics` (Boolean) Enable collection of agent metrics.

examples/resources/elasticstack_fleet_agent_policy/resource.tf

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ provider "elasticstack" {
33
}
44

55
resource "elasticstack_fleet_agent_policy" "test_policy" {
6-
name = "Test Policy"
7-
namespace = "default"
8-
description = "Test Agent Policy"
9-
sys_monitoring = true
10-
monitor_logs = true
11-
monitor_metrics = true
12-
space_ids = ["default"]
6+
name = "Test Policy"
7+
namespace = "default"
8+
description = "Test Agent Policy"
9+
sys_monitoring = true
10+
monitor_logs = true
11+
monitor_metrics = true
12+
space_ids = ["default"]
13+
host_name_format = "hostname" # or "fqdn" for fully qualified domain names
1314

1415
global_data_tags = {
1516
first_tag = {

internal/fleet/agent_policy/acc_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,62 @@ func checkResourceAgentPolicySkipDestroy(s *terraform.State) error {
525525
return nil
526526
}
527527

528+
func TestAccResourceAgentPolicyWithHostNameFormat(t *testing.T) {
529+
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
530+
531+
resource.Test(t, resource.TestCase{
532+
PreCheck: func() { acctest.PreCheck(t) },
533+
CheckDestroy: checkResourceAgentPolicyDestroy,
534+
Steps: []resource.TestStep{
535+
{
536+
// Step 1: Create with host_name_format = "fqdn"
537+
ProtoV6ProviderFactories: acctest.Providers,
538+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAgentFeatures),
539+
ConfigDirectory: acctest.NamedTestCaseDirectory("create_with_fqdn"),
540+
ConfigVariables: config.Variables{
541+
"policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
542+
},
543+
Check: resource.ComposeTestCheckFunc(
544+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
545+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
546+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "Test Agent Policy with FQDN host name format"),
547+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "host_name_format", "fqdn"),
548+
),
549+
},
550+
{
551+
// Step 2: Remove host_name_format from config - should use default "hostname"
552+
ProtoV6ProviderFactories: acctest.Providers,
553+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAgentFeatures),
554+
ConfigDirectory: acctest.NamedTestCaseDirectory("remove_host_name_format"),
555+
ConfigVariables: config.Variables{
556+
"policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
557+
},
558+
Check: resource.ComposeTestCheckFunc(
559+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
560+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
561+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "Test Agent Policy without host_name_format"),
562+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "host_name_format", "hostname"),
563+
),
564+
},
565+
{
566+
// Step 3: Explicitly set host_name_format = "hostname"
567+
ProtoV6ProviderFactories: acctest.Providers,
568+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionAgentFeatures),
569+
ConfigDirectory: acctest.NamedTestCaseDirectory("update_to_hostname"),
570+
ConfigVariables: config.Variables{
571+
"policy_name": config.StringVariable(fmt.Sprintf("Policy %s", policyName)),
572+
},
573+
Check: resource.ComposeTestCheckFunc(
574+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
575+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
576+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "Test Agent Policy with hostname format"),
577+
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "host_name_format", "hostname"),
578+
),
579+
},
580+
},
581+
})
582+
}
583+
528584
func TestAccResourceAgentPolicyWithRequiredVersions(t *testing.T) {
529585
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
530586

internal/fleet/agent_policy/models.go

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,29 @@ import (
1717
"github.com/hashicorp/terraform-plugin-framework/types"
1818
)
1919

20+
const (
21+
// HostNameFormatHostname represents the short hostname format (e.g., "myhost")
22+
HostNameFormatHostname = "hostname"
23+
// HostNameFormatFQDN represents the fully qualified domain name format (e.g., "myhost.example.com")
24+
HostNameFormatFQDN = "fqdn"
25+
// agentFeatureFQDN is the name of the agent feature that enables FQDN host name format
26+
agentFeatureFQDN = "fqdn"
27+
)
28+
29+
// apiAgentFeature is the type expected by the generated API for agent features
30+
type apiAgentFeature = struct {
31+
Enabled bool `json:"enabled"`
32+
Name string `json:"name"`
33+
}
34+
2035
type features struct {
2136
SupportsGlobalDataTags bool
2237
SupportsSupportsAgentless bool
2338
SupportsInactivityTimeout bool
2439
SupportsUnenrollmentTimeout bool
2540
SupportsSpaceIds bool
2641
SupportsRequiredVersions bool
42+
SupportsAgentFeatures bool
2743
}
2844

2945
type globalDataTagsItemModel struct {
@@ -45,6 +61,7 @@ type agentPolicyModel struct {
4561
MonitorMetrics types.Bool `tfsdk:"monitor_metrics"`
4662
SysMonitoring types.Bool `tfsdk:"sys_monitoring"`
4763
SkipDestroy types.Bool `tfsdk:"skip_destroy"`
64+
HostNameFormat types.String `tfsdk:"host_name_format"`
4865
SupportsAgentless types.Bool `tfsdk:"supports_agentless"`
4966
InactivityTimeout customtypes.Duration `tfsdk:"inactivity_timeout"`
5067
UnenrollmentTimeout customtypes.Duration `tfsdk:"unenrollment_timeout"`
@@ -84,6 +101,20 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.
84101
model.Name = types.StringValue(data.Name)
85102
model.Namespace = types.StringValue(data.Namespace)
86103
model.SupportsAgentless = types.BoolPointerValue(data.SupportsAgentless)
104+
105+
// Determine host_name_format from AgentFeatures
106+
// If AgentFeatures contains {"enabled": true, "name": "fqdn"}, then host_name_format is "fqdn"
107+
// Otherwise, it defaults to "hostname"
108+
model.HostNameFormat = types.StringValue(HostNameFormatHostname)
109+
if data.AgentFeatures != nil {
110+
for _, feature := range *data.AgentFeatures {
111+
if feature.Name == agentFeatureFQDN && feature.Enabled {
112+
model.HostNameFormat = types.StringValue(HostNameFormatFQDN)
113+
break
114+
}
115+
}
116+
}
117+
87118
if data.InactivityTimeout != nil {
88119
// Convert seconds to duration string
89120
seconds := int64(*data.InactivityTimeout)
@@ -377,10 +408,30 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat featur
377408
}
378409
body.RequiredVersions = requiredVersions
379410

411+
// Handle host_name_format via AgentFeatures
412+
if agentFeature := model.convertHostNameFormatToAgentFeature(); agentFeature != nil {
413+
if !feat.SupportsAgentFeatures {
414+
// Only error if user explicitly requests FQDN on unsupported version
415+
// Default "hostname" is fine - just don't send agent_features
416+
if agentFeature.Enabled {
417+
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diag.Diagnostics{
418+
diag.NewAttributeErrorDiagnostic(
419+
path.Root("host_name_format"),
420+
"Unsupported Elasticsearch version",
421+
fmt.Sprintf("host_name_format (agent_features) is only supported in Elastic Stack %s and above", MinVersionAgentFeatures),
422+
),
423+
}
424+
}
425+
// On unsupported version with default "hostname", don't send agent_features
426+
} else {
427+
body.AgentFeatures = &[]apiAgentFeature{*agentFeature}
428+
}
429+
}
430+
380431
return body, nil
381432
}
382433

383-
func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat features) (kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody, diag.Diagnostics) {
434+
func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat features, existingFeatures []apiAgentFeature) (kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody, diag.Diagnostics) {
384435
monitoring := make([]kbapi.PutFleetAgentPoliciesAgentpolicyidJSONBodyMonitoringEnabled, 0, 2)
385436
if model.MonitorLogs.ValueBool() {
386437
monitoring = append(monitoring, kbapi.PutFleetAgentPoliciesAgentpolicyidJSONBodyMonitoringEnabledLogs)
@@ -481,5 +532,76 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat featur
481532
}
482533
body.RequiredVersions = requiredVersions
483534

535+
// Handle host_name_format via AgentFeatures, preserving other existing features
536+
if agentFeature := model.convertHostNameFormatToAgentFeature(); agentFeature != nil {
537+
if !feat.SupportsAgentFeatures {
538+
// Only error if user explicitly requests FQDN on unsupported version
539+
// Default "hostname" is fine - just don't send agent_features
540+
if agentFeature.Enabled {
541+
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diag.Diagnostics{
542+
diag.NewAttributeErrorDiagnostic(
543+
path.Root("host_name_format"),
544+
"Unsupported Elasticsearch version",
545+
fmt.Sprintf("host_name_format (agent_features) is only supported in Elastic Stack %s and above", MinVersionAgentFeatures),
546+
),
547+
}
548+
}
549+
// On unsupported version with default "hostname", don't send agent_features
550+
} else {
551+
body.AgentFeatures = mergeAgentFeature(existingFeatures, agentFeature)
552+
}
553+
} else if feat.SupportsAgentFeatures && len(existingFeatures) > 0 {
554+
// Preserve existing features even when host_name_format is not set
555+
body.AgentFeatures = &existingFeatures
556+
}
557+
484558
return body, nil
485559
}
560+
561+
// convertHostNameFormatToAgentFeature converts the host_name_format field to a single AgentFeature.
562+
// - When host_name_format is "fqdn": returns {"name": "fqdn", "enabled": true}
563+
// - When host_name_format is "hostname": returns {"name": "fqdn", "enabled": false} to explicitly disable
564+
// - When not set: returns nil (no change to existing features)
565+
func (model *agentPolicyModel) convertHostNameFormatToAgentFeature() *apiAgentFeature {
566+
// If host_name_format is not set or unknown, don't modify AgentFeatures
567+
if model.HostNameFormat.IsNull() || model.HostNameFormat.IsUnknown() {
568+
return nil
569+
}
570+
571+
// Explicitly set enabled based on the host_name_format value
572+
// We need to send enabled: false when hostname is selected to override any existing fqdn setting
573+
return &apiAgentFeature{
574+
Enabled: model.HostNameFormat.ValueString() == HostNameFormatFQDN,
575+
Name: agentFeatureFQDN,
576+
}
577+
}
578+
579+
// mergeAgentFeature merges a single feature into existing features, replacing any feature with the same name.
580+
// If newFeature is nil, returns existing features unchanged (nil if existing is empty).
581+
func mergeAgentFeature(existing []apiAgentFeature, newFeature *apiAgentFeature) *[]apiAgentFeature {
582+
if newFeature == nil {
583+
if len(existing) == 0 {
584+
return nil
585+
}
586+
return &existing
587+
}
588+
589+
// Check if the feature already exists and replace it, otherwise append
590+
result := make([]apiAgentFeature, 0, len(existing)+1)
591+
found := false
592+
593+
for _, f := range existing {
594+
if f.Name == newFeature.Name {
595+
result = append(result, *newFeature)
596+
found = true
597+
} else {
598+
result = append(result, f)
599+
}
600+
}
601+
602+
if !found {
603+
result = append(result, *newFeature)
604+
}
605+
606+
return &result
607+
}

0 commit comments

Comments
 (0)