Skip to content

Support the supports_agentless field in Fleet agent policies. #1197

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- Add support for `timeslice_metric_indicator` in `elasticstack_kibana_slo` ([#1195](https://github.com/elastic/terraform-provider-elasticstack/pull/1195))
- Add `elasticstack_elasticsearch_ingest_processor_reroute` data source ([#678](https://github.com/elastic/terraform-provider-elasticstack/issues/678))
- Add support for `supports_agentless` to `elasticstack_fleet_agent_policy` ([#1197](https://github.com/elastic/terraform-provider-elasticstack/pull/1197))
- Ignore `master_timeout` when targeting Serverless projects ([#1207](https://github.com/elastic/terraform-provider-elasticstack/pull/1207))

## [0.11.16] - 2025-07-09

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 @@ -56,6 +56,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" {
- `monitoring_output_id` (String) The identifier for monitoring output.
- `policy_id` (String) Unique identifier of the agent policy.
- `skip_destroy` (Boolean) Set to true if you do not wish the agent policy to be deleted at destroy time, and instead just remove the agent policy from the Terraform state.
- `supports_agentless` (Boolean) Set to true to enable agentless data collection.
- `sys_monitoring` (Boolean) Enable collection of system logs and metrics.

### Read-Only
Expand Down
20 changes: 20 additions & 0 deletions internal/clients/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type CompositeId struct {
ResourceId string
}

const ServerlessFlavor = "serverless"

func CompositeIdFromStr(id string) (*CompositeId, diag.Diagnostics) {
var diags diag.Diagnostics
idParts := strings.Split(id, "/")
Expand Down Expand Up @@ -352,6 +354,24 @@ func (a *ApiClient) serverInfo(ctx context.Context) (*models.ClusterInfo, diag.D
return &info, diags
}

func (a *ApiClient) EnforceMinVersion(ctx context.Context, minVersion *version.Version) (bool, diag.Diagnostics) {
flavor, diags := a.ServerFlavor(ctx)
if diags.HasError() {
return false, diags
}

if flavor == ServerlessFlavor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the func's behavior doesn't correspond to its name (or vice versa), e.g. if server flavor is "serverless" the func doesn't enforce min version.

return true, nil
}

serverVersion, diags := a.ServerVersion(ctx)
if diags.HasError() {
return false, diags
}

return serverVersion.GreaterThanOrEqual(minVersion), nil
}

func (a *ApiClient) ServerVersion(ctx context.Context) (*version.Version, diag.Diagnostics) {
if a.elasticsearch != nil {
return a.versionFromElasticsearch(ctx)
Expand Down
2 changes: 1 addition & 1 deletion internal/elasticsearch/index/index/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func (model tfModel) toPutIndexParams(serverFlavor string) models.PutIndexParams
Timeout: timeout,
}

if serverFlavor != "serverless" {
if serverFlavor != clients.ServerlessFlavor {
params.MasterTimeout = masterTimeout
params.WaitForActiveShards = model.WaitForActiveShards.ValueString()
}
Expand Down
3 changes: 2 additions & 1 deletion internal/elasticsearch/index/index/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

fuzz "github.com/google/gofuzz"

"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/models"
"github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
Expand Down Expand Up @@ -363,7 +364,7 @@ func Test_tfModel_toPutIndexParams(t *testing.T) {

flavor := "not_serverless"
if isServerless {
flavor = "serverless"
flavor = clients.ServerlessFlavor
expectedParams.WaitForActiveShards = ""
expectedParams.MasterTimeout = 0
}
Expand Down
7 changes: 4 additions & 3 deletions internal/fleet/agent_policy/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ func (r *agentPolicyResource) Create(ctx context.Context, req resource.CreateReq
return
}

sVersion, e := r.client.ServerVersion(ctx)
if e != nil {
feat, diags := r.buildFeatures(ctx)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

body, diags := planModel.toAPICreateModel(ctx, sVersion)
body, diags := planModel.toAPICreateModel(ctx, feat)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
Expand Down
44 changes: 36 additions & 8 deletions internal/fleet/agent_policy/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import (
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"

"github.com/hashicorp/go-version"
"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/types"
)

type features struct {
SupportsGlobalDataTags bool
SupportsSupportsAgentless bool
}

type globalDataTagsItemModel struct {
StringValue types.String `tfsdk:"string_value"`
NumberValue types.Float32 `tfsdk:"number_value"`
Expand All @@ -34,6 +38,7 @@ type agentPolicyModel struct {
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
}

Expand Down Expand Up @@ -67,6 +72,7 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.
model.MonitoringOutputId = types.StringPointerValue(data.MonitoringOutputId)
model.Name = types.StringValue(data.Name)
model.Namespace = types.StringValue(data.Namespace)
model.SupportsAgentless = types.BoolPointerValue(data.SupportsAgentless)
if utils.Deref(data.GlobalDataTags) != nil {
diags := diag.Diagnostics{}
var map0 = make(map[string]globalDataTagsItemModel)
Expand Down Expand Up @@ -99,18 +105,18 @@ func (model *agentPolicyModel) populateFromAPI(ctx context.Context, data *kbapi.

// convertGlobalDataTags converts the global data tags from terraform model to API model
// and performs version validation
func (model *agentPolicyModel) convertGlobalDataTags(ctx context.Context, serverVersion *version.Version) (*[]kbapi.AgentPolicyGlobalDataTagsItem, diag.Diagnostics) {
func (model *agentPolicyModel) convertGlobalDataTags(ctx context.Context, feat features) (*[]kbapi.AgentPolicyGlobalDataTagsItem, diag.Diagnostics) {
var diags diag.Diagnostics

if len(model.GlobalDataTags.Elements()) == 0 {
if serverVersion.GreaterThanOrEqual(MinVersionGlobalDataTags) {
if feat.SupportsGlobalDataTags {
emptyList := make([]kbapi.AgentPolicyGlobalDataTagsItem, 0)
return &emptyList, diags
}
return nil, diags
}

if serverVersion.LessThan(MinVersionGlobalDataTags) {
if !feat.SupportsGlobalDataTags {
diags.AddError("global_data_tags ES version error", fmt.Sprintf("Global data tags are only supported in Elastic Stack %s and above", MinVersionGlobalDataTags))
return nil, diags
}
Expand Down Expand Up @@ -146,7 +152,7 @@ func (model *agentPolicyModel) convertGlobalDataTags(ctx context.Context, server
return &itemsList, diags
}

func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, serverVersion *version.Version) (kbapi.PostFleetAgentPoliciesJSONRequestBody, diag.Diagnostics) {
func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, feat features) (kbapi.PostFleetAgentPoliciesJSONRequestBody, diag.Diagnostics) {
monitoring := make([]kbapi.PostFleetAgentPoliciesJSONBodyMonitoringEnabled, 0, 2)

if model.MonitorLogs.ValueBool() {
Expand All @@ -156,6 +162,16 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, serverVersi
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 @@ -166,9 +182,10 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, serverVersi
MonitoringOutputId: model.MonitoringOutputId.ValueStringPointer(),
Name: model.Name.ValueString(),
Namespace: model.Namespace.ValueString(),
SupportsAgentless: model.SupportsAgentless.ValueBoolPointer(),
}

tags, diags := model.convertGlobalDataTags(ctx, serverVersion)
tags, diags := model.convertGlobalDataTags(ctx, feat)
if diags.HasError() {
return kbapi.PostFleetAgentPoliciesJSONRequestBody{}, diags
}
Expand All @@ -177,7 +194,7 @@ func (model *agentPolicyModel) toAPICreateModel(ctx context.Context, serverVersi
return body, nil
}

func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, serverVersion *version.Version) (kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody, diag.Diagnostics) {
func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, feat features) (kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody, diag.Diagnostics) {
monitoring := make([]kbapi.PutFleetAgentPoliciesAgentpolicyidJSONBodyMonitoringEnabled, 0, 2)
if model.MonitorLogs.ValueBool() {
monitoring = append(monitoring, kbapi.Logs)
Expand All @@ -186,6 +203,16 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, serverVersi
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 @@ -195,9 +222,10 @@ func (model *agentPolicyModel) toAPIUpdateModel(ctx context.Context, serverVersi
MonitoringOutputId: model.MonitoringOutputId.ValueStringPointer(),
Name: model.Name.ValueString(),
Namespace: model.Namespace.ValueString(),
SupportsAgentless: model.SupportsAgentless.ValueBoolPointer(),
}

tags, diags := model.convertGlobalDataTags(ctx, serverVersion)
tags, diags := model.convertGlobalDataTags(ctx, feat)
if diags.HasError() {
return kbapi.PutFleetAgentPoliciesAgentpolicyidJSONRequestBody{}, diags
}
Expand Down
24 changes: 23 additions & 1 deletion internal/fleet/agent_policy/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"

"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
)
Expand All @@ -16,7 +18,10 @@ var (
_ resource.ResourceWithImportState = &agentPolicyResource{}
)

var MinVersionGlobalDataTags = version.Must(version.NewVersion("8.15.0"))
var (
MinVersionGlobalDataTags = version.Must(version.NewVersion("8.15.0"))
MinSupportsAgentlessVersion = version.Must(version.NewVersion("8.15.0"))
)

// NewResource is a helper function to simplify the provider implementation.
func NewResource() resource.Resource {
Expand All @@ -40,3 +45,20 @@ func (r *agentPolicyResource) Metadata(ctx context.Context, req resource.Metadat
func (r *agentPolicyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("policy_id"), req, resp)
}

func (r *agentPolicyResource) buildFeatures(ctx context.Context) (features, diag.Diagnostics) {
supportsGDT, diags := r.client.EnforceMinVersion(ctx, MinVersionGlobalDataTags)
if diags.HasError() {
return features{}, utils.FrameworkDiagsFromSDK(diags)
}

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

return features{
SupportsGlobalDataTags: supportsGDT,
SupportsSupportsAgentless: supportsSupportsAgentless,
}, nil
}
Loading
Loading