Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Add support for `unenrollment_timeout` in `elasticstack_fleet_agent_policy` ([#1169](https://github.com/elastic/terraform-provider-elasticstack/issues/1169))
- Handle default value for `allow_restricted_indices` in `elasticstack_elasticsearch_security_api_key` ([#1315](https://github.com/elastic/terraform-provider-elasticstack/pull/1315))
- Fixed `nil` reference in kibana synthetics API client in case of response errors ([#1320](https://github.com/elastic/terraform-provider-elasticstack/pull/1320))
- Add support for `agent_policy_ids` in `elasticstack_fleet_integration_policy` ([#1131](https://github.com/elastic/terraform-provider-elasticstack/pull/1311))

## [0.11.17] - 2025-07-21

Expand Down
3 changes: 2 additions & 1 deletion docs/resources/fleet_integration_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,15 @@ resource "elasticstack_fleet_integration_policy" "sample" {

### Required

- `agent_policy_id` (String) ID of the agent policy.
- `integration_name` (String) The name of the integration package.
- `integration_version` (String) The version of the integration package.
- `name` (String) The name of the integration policy.
- `namespace` (String) The namespace of the integration policy.

### Optional

- `agent_policy_id` (String) ID of the agent policy.
- `agent_policy_ids` (List of String) List of agent policy IDs.
- `description` (String) The description of the integration policy.
- `enabled` (Boolean) Enable the integration policy.
- `force` (Boolean) Force operations, such as creation and deletion, to occur.
Expand Down
11,045 changes: 10,376 additions & 669 deletions generated/kbapi/kibana.gen.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions generated/kbapi/transform_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ var transformers = []TransformFunc{
fixPutSecurityRoleName,
fixGetSpacesParams,
fixGetSyntheticsMonitorsParams,
fixGetMaintenanceWindowFindParams,
transformRemoveExamples,
transformRemoveUnusedComponents,
transformOmitEmptyNullable,
Expand Down Expand Up @@ -909,6 +910,10 @@ func fixGetSyntheticsMonitorsParams(schema *Schema) {
schema.MustGetPath("/api/synthetics/monitors").MustGetEndpoint("get").Move("parameters.12.schema.oneOf.1", "parameters.12.schema")
}

func fixGetMaintenanceWindowFindParams(schema *Schema) {
schema.MustGetPath("/api/maintenance_window/_find").MustGetEndpoint("get").Move("parameters.2.schema.anyOf.1", "parameters.2.schema")
}

// transformFleetPaths fixes the fleet paths.
func transformFleetPaths(schema *Schema) {
// Agent policies
Expand Down
8 changes: 7 additions & 1 deletion internal/fleet/integration_policy/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ func (r *integrationPolicyResource) Create(ctx context.Context, req resource.Cre
return
}

body, diags := planModel.toAPIModel(ctx, false)
feat, diags := r.buildFeatures(ctx)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

body, diags := planModel.toAPIModel(ctx, false, feat)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
Expand Down
72 changes: 69 additions & 3 deletions internal/fleet/integration_policy/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package integration_policy

import (
"context"
"fmt"
"sort"

"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
Expand All @@ -12,12 +13,17 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

type features struct {
SupportsPolicyIds bool
}

type integrationPolicyModel struct {
ID types.String `tfsdk:"id"`
PolicyID types.String `tfsdk:"policy_id"`
Name types.String `tfsdk:"name"`
Namespace types.String `tfsdk:"namespace"`
AgentPolicyID types.String `tfsdk:"agent_policy_id"`
AgentPolicyIDs types.List `tfsdk:"agent_policy_ids"`
Description types.String `tfsdk:"description"`
Enabled types.Bool `tfsdk:"enabled"`
Force types.Bool `tfsdk:"force"`
Expand Down Expand Up @@ -45,7 +51,40 @@ func (model *integrationPolicyModel) populateFromAPI(ctx context.Context, data *
model.PolicyID = types.StringValue(data.Id)
model.Name = types.StringValue(data.Name)
model.Namespace = types.StringPointerValue(data.Namespace)
model.AgentPolicyID = types.StringPointerValue(data.PolicyId)

// Only populate the agent policy field that was originally configured
// to avoid Terraform detecting inconsistent state

originallyUsedAgentPolicyID := utils.IsKnown(model.AgentPolicyID)
originallyUsedAgentPolicyIDs := utils.IsKnown(model.AgentPolicyIDs)

if originallyUsedAgentPolicyID {
model.AgentPolicyID = types.StringPointerValue(data.PolicyId)
}
if originallyUsedAgentPolicyIDs {
if data.PolicyIds != nil {
agentPolicyIDs, d := types.ListValueFrom(ctx, types.StringType, *data.PolicyIds)
diags.Append(d...)
model.AgentPolicyIDs = agentPolicyIDs
} else {
model.AgentPolicyIDs = types.ListNull(types.StringType)
}
}

if !originallyUsedAgentPolicyID && !originallyUsedAgentPolicyIDs {
// Handle edge cases: both fields configured or neither configured
// Default to the behavior based on API response structure
if data.PolicyIds != nil && len(*data.PolicyIds) > 1 {
// Multiple policy IDs, use agent_policy_ids
agentPolicyIDs, d := types.ListValueFrom(ctx, types.StringType, *data.PolicyIds)
diags.Append(d...)
model.AgentPolicyIDs = agentPolicyIDs
} else if data.PolicyId != nil {
// Single policy ID, use agent_policy_id
model.AgentPolicyID = types.StringPointerValue(data.PolicyId)
}
}

model.Description = types.StringPointerValue(data.Description)
model.Enabled = types.BoolValue(data.Enabled)
model.IntegrationName = types.StringValue(data.Package.Name)
Expand Down Expand Up @@ -81,9 +120,22 @@ func (model *integrationPolicyModel) populateInputFromAPI(ctx context.Context, i
}
}

func (model integrationPolicyModel) toAPIModel(ctx context.Context, isUpdate bool) (kbapi.PackagePolicyRequest, diag.Diagnostics) {
func (model integrationPolicyModel) toAPIModel(ctx context.Context, isUpdate bool, feat features) (kbapi.PackagePolicyRequest, diag.Diagnostics) {
var diags diag.Diagnostics

// Check if agent_policy_ids is configured and version supports it
if utils.IsKnown(model.AgentPolicyIDs) {
if !feat.SupportsPolicyIds {
return kbapi.PackagePolicyRequest{}, diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("agent_policy_ids"),
"Unsupported Elasticsearch version",
fmt.Sprintf("Agent policy IDs are only supported in Elastic Stack %s and above", MinVersionPolicyIds),
),
}
}
}

body := kbapi.PackagePolicyRequest{
Description: model.Description.ValueStringPointer(),
Force: model.Force.ValueBoolPointer(),
Expand All @@ -94,7 +146,21 @@ func (model integrationPolicyModel) toAPIModel(ctx context.Context, isUpdate boo
Version: model.IntegrationVersion.ValueString(),
},
PolicyId: model.AgentPolicyID.ValueStringPointer(),
Vars: utils.MapRef(utils.NormalizedTypeToMap[any](model.VarsJson, path.Root("vars_json"), &diags)),
PolicyIds: func() *[]string {
if !model.AgentPolicyIDs.IsNull() && !model.AgentPolicyIDs.IsUnknown() {
var policyIDs []string
d := model.AgentPolicyIDs.ElementsAs(ctx, &policyIDs, false)
diags.Append(d...)
return &policyIDs
}
// Only return empty array for 8.15+ when agent_policy_ids is not defined
if feat.SupportsPolicyIds {
emptyArray := []string{}
return &emptyArray
}
return nil
}(),
Vars: utils.MapRef(utils.NormalizedTypeToMap[any](model.VarsJson, path.Root("vars_json"), &diags)),
}

if isUpdate {
Expand Down
18 changes: 18 additions & 0 deletions internal/fleet/integration_policy/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"fmt"

"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/diagutil"
"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,6 +19,10 @@ var (
_ resource.ResourceWithUpgradeState = &integrationPolicyResource{}
)

var (
MinVersionPolicyIds = version.Must(version.NewVersion("8.15.0"))
)

// NewResource is a helper function to simplify the provider implementation.
func NewResource() resource.Resource {
return &integrationPolicyResource{}
Expand Down Expand Up @@ -44,3 +51,14 @@ func (r *integrationPolicyResource) UpgradeState(context.Context) map[int64]reso
0: {PriorSchema: getSchemaV0(), StateUpgrader: upgradeV0},
}
}

func (r *integrationPolicyResource) buildFeatures(ctx context.Context) (features, diag.Diagnostics) {
supportsPolicyIds, diags := r.client.EnforceMinVersion(ctx, MinVersionPolicyIds)
if diags.HasError() {
return features{}, diagutil.FrameworkDiagsFromSDK(diags)
}

return features{
SupportsPolicyIds: supportsPolicyIds,
}, nil
}
86 changes: 84 additions & 2 deletions internal/fleet/integration_policy/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import (
)

var (
minVersionIntegrationPolicy = version.Must(version.NewVersion("8.10.0"))
minVersionSqlIntegration = version.Must(version.NewVersion("9.1.0"))
minVersionIntegrationPolicy = version.Must(version.NewVersion("8.10.0"))
minVersionIntegrationPolicyIds = version.Must(version.NewVersion("8.15.0"))
minVersionSqlIntegration = version.Must(version.NewVersion("9.1.0"))
)

func TestJsonTypes(t *testing.T) {
Expand All @@ -33,6 +34,29 @@ func TestJsonTypes(t *testing.T) {
require.False(t, equal)
}

func TestAccResourceIntegrationPolicyMultipleAgentPolicies(t *testing.T) {
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
CheckDestroy: checkResourceIntegrationPolicyDestroy,
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionIntegrationPolicyIds),
Config: testAccResourceIntegrationPolicyCreateMultipleAgentPolicies(policyName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "name", policyName),
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "description", "IntegrationPolicyTest Policy"),
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_name", "tcp"),
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_version", "1.16.0"),
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "agent_policy_ids.#", "2"),
),
},
},
})
}

func TestAccResourceIntegrationPolicy(t *testing.T) {
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)

Expand Down Expand Up @@ -494,6 +518,64 @@ resource "elasticstack_fleet_integration_policy" "test_policy" {
`, common, id, key, id)
}

func testAccResourceIntegrationPolicyCreateMultipleAgentPolicies(id string) string {
return fmt.Sprintf(`
provider "elasticstack" {
elasticsearch {}
kibana {}
}
resource "elasticstack_fleet_integration" "test_policy" {
name = "tcp"
version = "1.16.0"
force = true
}
resource "elasticstack_fleet_agent_policy" "test_policy_1" {
name = "%s Agent Policy 1"
namespace = "default"
description = "IntegrationPolicyTest Agent Policy 1"
monitor_logs = true
monitor_metrics = true
skip_destroy = false
}
resource "elasticstack_fleet_agent_policy" "test_policy_2" {
name = "%s Agent Policy 2"
namespace = "default"
description = "IntegrationPolicyTest Agent Policy 2"
monitor_logs = true
monitor_metrics = true
skip_destroy = false
}
resource "elasticstack_fleet_integration_policy" "test_policy" {
name = "%s"
namespace = "default"
description = "IntegrationPolicyTest Policy"
agent_policy_ids = [
elasticstack_fleet_agent_policy.test_policy_1.policy_id,
elasticstack_fleet_agent_policy.test_policy_2.policy_id
]
integration_name = elasticstack_fleet_integration.test_policy.name
integration_version = elasticstack_fleet_integration.test_policy.version
input {
input_id = "tcp-tcp"
streams_json = jsonencode({
"tcp.generic": {
"enabled": true
"vars": {
"listen_address": "localhost"
"listen_port": 8080
"data_stream.dataset": "tcp.generic"
"tags": []
"syslog_options": "field: message"
"ssl": ""
"custom": ""
}
}
})
}
}
`, id, id, id)
}

func testAccResourceIntegrationPolicySecretsIds(id string, key string) string {
common := testAccResourceIntegrationPolicyCommon(id, "sql", "1.1.0")
return fmt.Sprintf(`
Expand Down
19 changes: 18 additions & 1 deletion internal/fleet/integration_policy/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import (
_ "embed"

"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

//go:embed resource-description.md
Expand Down Expand Up @@ -51,7 +56,19 @@ func getSchemaV1() schema.Schema {
},
"agent_policy_id": schema.StringAttribute{
Description: "ID of the agent policy.",
Required: true,
Optional: true,
Validators: []validator.String{
stringvalidator.ConflictsWith(path.Root("agent_policy_ids").Expression()),
},
},
"agent_policy_ids": schema.ListAttribute{
Description: "List of agent policy IDs.",
ElementType: types.StringType,
Optional: true,
Validators: []validator.List{
listvalidator.ConflictsWith(path.Root("agent_policy_id").Expression()),
listvalidator.SizeAtLeast(1),
},
},
"description": schema.StringAttribute{
Description: "The description of the integration policy.",
Expand Down
8 changes: 7 additions & 1 deletion internal/fleet/integration_policy/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ func (r *integrationPolicyResource) Update(ctx context.Context, req resource.Upd
return
}

body, diags := planModel.toAPIModel(ctx, true)
feat, diags := r.buildFeatures(ctx)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

body, diags := planModel.toAPIModel(ctx, true, feat)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
Expand Down
Loading