Skip to content

Commit 0f8ba5e

Browse files
authored
Add output_id to the schema (#1445)
* Add output_id to the schema * CHANGELOG.md
1 parent 3cae061 commit 0f8ba5e

File tree

10 files changed

+358
-0
lines changed

10 files changed

+358
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Add back missing import support for `elasticstack_elasticsearch_security_role_mapping` ([#1441](https://github.com/elastic/terraform-provider-elasticstack/pull/1441))
88
- Add new `elasticstack_elasticsearch_ml_job_state` resource ([#1337](https://github.com/elastic/terraform-provider-elasticstack/pull/1337))
99
- Add new `elasticstack_elasticsearch_ml_datafeed_state` resource ([#1422](https://github.com/elastic/terraform-provider-elasticstack/pull/1422))
10+
- Add `output_id` to `elasticstack_fleet_integration_policy` resource ([#1445](https://github.com/elastic/terraform-provider-elasticstack/pull/1445))
1011
- Make `hosts` attribute required in `elasticstack_fleet_output` resource ([#1450](https://github.com/elastic/terraform-provider-elasticstack/pull/1450/files))
1112

1213
## [0.12.1] - 2025-10-22

docs/resources/fleet_integration_policy.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ resource "elasticstack_fleet_integration_policy" "sample" {
6363
agent_policy_id = elasticstack_fleet_agent_policy.sample.policy_id
6464
integration_name = elasticstack_fleet_integration.sample.name
6565
integration_version = elasticstack_fleet_integration.sample.version
66+
// Optional: specify a custom output to send data to
67+
// output_id = "my-custom-output-id"
6668
6769
input {
6870
input_id = "tcp-tcp"
@@ -102,6 +104,7 @@ resource "elasticstack_fleet_integration_policy" "sample" {
102104
- `enabled` (Boolean) Enable the integration policy.
103105
- `force` (Boolean) Force operations, such as creation and deletion, to occur.
104106
- `input` (Block List) Integration inputs. (see [below for nested schema](#nestedblock--input))
107+
- `output_id` (String) The ID of the output to send data to. When not specified, the default output of the agent policy will be used.
105108
- `policy_id` (String) Unique identifier of the integration policy.
106109
- `space_ids` (Set of String) The Kibana space IDs where this integration policy is available. When set, must match the space_ids of the referenced agent policy. If not set, will be inherited from the agent policy. Note: The order of space IDs does not matter as this is a set.
107110
- `vars_json` (String, Sensitive) Integration-level variables as JSON.

examples/resources/elasticstack_fleet_integration_policy/resource.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ resource "elasticstack_fleet_integration_policy" "sample" {
3232
agent_policy_id = elasticstack_fleet_agent_policy.sample.policy_id
3333
integration_name = elasticstack_fleet_integration.sample.name
3434
integration_version = elasticstack_fleet_integration.sample.version
35+
// Optional: specify a custom output to send data to
36+
// output_id = "my-custom-output-id"
3537

3638
input {
3739
input_id = "tcp-tcp"

internal/fleet/integration_policy/acc_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
var (
2626
minVersionIntegrationPolicy = version.Must(version.NewVersion("8.10.0"))
2727
minVersionIntegrationPolicyIds = version.Must(version.NewVersion("8.15.0"))
28+
minVersionOutputId = version.Must(version.NewVersion("8.16.0"))
2829
minVersionSqlIntegration = version.Must(version.NewVersion("9.1.0"))
2930
)
3031

@@ -62,6 +63,56 @@ func TestAccResourceIntegrationPolicyMultipleAgentPolicies(t *testing.T) {
6263
})
6364
}
6465

66+
func TestAccResourceIntegrationPolicyWithOutput(t *testing.T) {
67+
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
68+
69+
resource.Test(t, resource.TestCase{
70+
PreCheck: func() { acctest.PreCheck(t) },
71+
CheckDestroy: checkResourceIntegrationPolicyDestroy,
72+
Steps: []resource.TestStep{
73+
{
74+
ProtoV6ProviderFactories: acctest.Providers,
75+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionOutputId),
76+
ConfigDirectory: acctest.NamedTestCaseDirectory("create"),
77+
ConfigVariables: config.Variables{
78+
"policy_name": config.StringVariable(policyName),
79+
"output_name": config.StringVariable(fmt.Sprintf("Test Output %s", policyName)),
80+
},
81+
Check: resource.ComposeTestCheckFunc(
82+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "name", policyName),
83+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "description", "IntegrationPolicyTest Policy with Output"),
84+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_name", "tcp"),
85+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_version", "1.16.0"),
86+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "output_id", fmt.Sprintf("%s-test-output", policyName)),
87+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.input_id", "tcp-tcp"),
88+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.enabled", "true"),
89+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.streams_json", `{"tcp.generic":{"enabled":true,"vars":{"custom":"","data_stream.dataset":"tcp.generic","listen_address":"localhost","listen_port":8080,"ssl":"","syslog_options":"field: message","tags":[]}}}`),
90+
),
91+
},
92+
{
93+
ProtoV6ProviderFactories: acctest.Providers,
94+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionOutputId),
95+
ConfigDirectory: acctest.NamedTestCaseDirectory("update"),
96+
ConfigVariables: config.Variables{
97+
"policy_name": config.StringVariable(policyName),
98+
"output_name": config.StringVariable(fmt.Sprintf("Test Output %s", policyName)),
99+
"updated_output_name": config.StringVariable(fmt.Sprintf("Updated Test Output %s", policyName)),
100+
},
101+
Check: resource.ComposeTestCheckFunc(
102+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "name", policyName),
103+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "description", "Updated Integration Policy with Output"),
104+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_name", "tcp"),
105+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_version", "1.16.0"),
106+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "output_id", fmt.Sprintf("%s-updated-output", policyName)),
107+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.input_id", "tcp-tcp"),
108+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.enabled", "false"),
109+
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.streams_json", `{"tcp.generic":{"enabled":false,"vars":{"custom":"","data_stream.dataset":"tcp.generic","listen_address":"localhost","listen_port":8085,"ssl":"","syslog_options":"field: message","tags":[]}}}`),
110+
),
111+
},
112+
},
113+
})
114+
}
115+
65116
func TestAccResourceIntegrationPolicy(t *testing.T) {
66117
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
67118

@@ -82,6 +133,7 @@ func TestAccResourceIntegrationPolicy(t *testing.T) {
82133
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_name", "tcp"),
83134
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_version", "1.16.0"),
84135
resource.TestCheckNoResourceAttr("elasticstack_fleet_integration_policy.test_policy", "vars_json"),
136+
resource.TestCheckNoResourceAttr("elasticstack_fleet_integration_policy.test_policy", "output_id"),
85137
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.input_id", "tcp-tcp"),
86138
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.enabled", "true"),
87139
resource.TestCheckNoResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.vars_json"),
@@ -101,6 +153,7 @@ func TestAccResourceIntegrationPolicy(t *testing.T) {
101153
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_name", "tcp"),
102154
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "integration_version", "1.16.0"),
103155
resource.TestCheckNoResourceAttr("elasticstack_fleet_integration_policy.test_policy", "vars_json"),
156+
resource.TestCheckNoResourceAttr("elasticstack_fleet_integration_policy.test_policy", "output_id"),
104157
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.input_id", "tcp-tcp"),
105158
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.enabled", "false"),
106159
resource.TestCheckNoResourceAttr("elasticstack_fleet_integration_policy.test_policy", "input.0.vars_json"),

internal/fleet/integration_policy/models.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
type features struct {
1717
SupportsPolicyIds bool
18+
SupportsOutputId bool
1819
}
1920

2021
type integrationPolicyModel struct {
@@ -29,6 +30,7 @@ type integrationPolicyModel struct {
2930
Force types.Bool `tfsdk:"force"`
3031
IntegrationName types.String `tfsdk:"integration_name"`
3132
IntegrationVersion types.String `tfsdk:"integration_version"`
33+
OutputID types.String `tfsdk:"output_id"`
3234
Input types.List `tfsdk:"input"` //> integrationPolicyInputModel
3335
VarsJson jsontypes.Normalized `tfsdk:"vars_json"`
3436
SpaceIds types.Set `tfsdk:"space_ids"`
@@ -90,6 +92,7 @@ func (model *integrationPolicyModel) populateFromAPI(ctx context.Context, data *
9092
model.Enabled = types.BoolValue(data.Enabled)
9193
model.IntegrationName = types.StringValue(data.Package.Name)
9294
model.IntegrationVersion = types.StringValue(data.Package.Version)
95+
model.OutputID = types.StringPointerValue(data.OutputId)
9396
model.VarsJson = utils.MapToNormalizedType(utils.Deref(data.Vars), path.Root("vars_json"), &diags)
9497

9598
// Preserve space_ids if it was originally set in the plan/state
@@ -170,11 +173,25 @@ func (model integrationPolicyModel) toAPIModel(ctx context.Context, isUpdate boo
170173
}
171174
}
172175

176+
// Check if output_id is configured and version supports it
177+
if utils.IsKnown(model.OutputID) {
178+
if !feat.SupportsOutputId {
179+
return kbapi.PackagePolicyRequest{}, diag.Diagnostics{
180+
diag.NewAttributeErrorDiagnostic(
181+
path.Root("output_id"),
182+
"Unsupported Elasticsearch version",
183+
fmt.Sprintf("Output ID is only supported in Elastic Stack %s and above", MinVersionOutputId),
184+
),
185+
}
186+
}
187+
}
188+
173189
body := kbapi.PackagePolicyRequest{
174190
Description: model.Description.ValueStringPointer(),
175191
Force: model.Force.ValueBoolPointer(),
176192
Name: model.Name.ValueString(),
177193
Namespace: model.Namespace.ValueStringPointer(),
194+
OutputId: model.OutputID.ValueStringPointer(),
178195
Package: kbapi.PackagePolicyRequestPackage{
179196
Name: model.IntegrationName.ValueString(),
180197
Version: model.IntegrationVersion.ValueString(),

internal/fleet/integration_policy/models_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package integration_policy
22

33
import (
4+
"context"
45
"testing"
56

7+
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
68
"github.com/hashicorp/terraform-plugin-framework/types"
79
"github.com/stretchr/testify/require"
810
)
@@ -62,3 +64,77 @@ func Test_SortInputs(t *testing.T) {
6264
require.Equal(t, want, incoming)
6365
})
6466
}
67+
68+
func TestOutputIdHandling(t *testing.T) {
69+
t.Run("populateFromAPI", func(t *testing.T) {
70+
model := &integrationPolicyModel{}
71+
outputId := "test-output-id"
72+
data := &kbapi.PackagePolicy{
73+
Id: "test-id",
74+
Name: "test-policy",
75+
Enabled: true,
76+
Package: &struct {
77+
ExperimentalDataStreamFeatures *[]struct {
78+
DataStream string `json:"data_stream"`
79+
Features struct {
80+
DocValueOnlyNumeric *bool `json:"doc_value_only_numeric,omitempty"`
81+
DocValueOnlyOther *bool `json:"doc_value_only_other,omitempty"`
82+
SyntheticSource *bool `json:"synthetic_source,omitempty"`
83+
Tsdb *bool `json:"tsdb,omitempty"`
84+
} `json:"features"`
85+
} `json:"experimental_data_stream_features,omitempty"`
86+
FipsCompatible *bool `json:"fips_compatible,omitempty"`
87+
Name string `json:"name"`
88+
RequiresRoot *bool `json:"requires_root,omitempty"`
89+
Title *string `json:"title,omitempty"`
90+
Version string `json:"version"`
91+
}{
92+
Name: "test-integration",
93+
Version: "1.0.0",
94+
},
95+
OutputId: &outputId,
96+
}
97+
98+
diags := model.populateFromAPI(context.Background(), data)
99+
require.Empty(t, diags)
100+
require.Equal(t, "test-output-id", model.OutputID.ValueString())
101+
})
102+
103+
t.Run("toAPIModel", func(t *testing.T) {
104+
model := integrationPolicyModel{
105+
Name: types.StringValue("test-policy"),
106+
IntegrationName: types.StringValue("test-integration"),
107+
IntegrationVersion: types.StringValue("1.0.0"),
108+
OutputID: types.StringValue("test-output-id"),
109+
}
110+
111+
feat := features{
112+
SupportsPolicyIds: true,
113+
SupportsOutputId: true,
114+
}
115+
116+
result, diags := model.toAPIModel(context.Background(), false, feat)
117+
require.Empty(t, diags)
118+
require.NotNil(t, result.OutputId)
119+
require.Equal(t, "test-output-id", *result.OutputId)
120+
})
121+
122+
t.Run("toAPIModel_unsupported_version", func(t *testing.T) {
123+
model := integrationPolicyModel{
124+
Name: types.StringValue("test-policy"),
125+
IntegrationName: types.StringValue("test-integration"),
126+
IntegrationVersion: types.StringValue("1.0.0"),
127+
OutputID: types.StringValue("test-output-id"),
128+
}
129+
130+
feat := features{
131+
SupportsPolicyIds: true,
132+
SupportsOutputId: false, // Simulate unsupported version
133+
}
134+
135+
_, diags := model.toAPIModel(context.Background(), false, feat)
136+
require.Len(t, diags, 1)
137+
require.Equal(t, "Unsupported Elasticsearch version", diags[0].Summary())
138+
require.Contains(t, diags[0].Detail(), "Output ID is only supported in Elastic Stack")
139+
})
140+
}

internal/fleet/integration_policy/resource.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var (
2121

2222
var (
2323
MinVersionPolicyIds = version.Must(version.NewVersion("8.15.0"))
24+
MinVersionOutputId = version.Must(version.NewVersion("8.16.0"))
2425
)
2526

2627
// NewResource is a helper function to simplify the provider implementation.
@@ -58,7 +59,13 @@ func (r *integrationPolicyResource) buildFeatures(ctx context.Context) (features
5859
return features{}, diagutil.FrameworkDiagsFromSDK(diags)
5960
}
6061

62+
supportsOutputId, outputIdDiags := r.client.EnforceMinVersion(ctx, MinVersionOutputId)
63+
if outputIdDiags.HasError() {
64+
return features{}, diagutil.FrameworkDiagsFromSDK(outputIdDiags)
65+
}
66+
6167
return features{
6268
SupportsPolicyIds: supportsPolicyIds,
69+
SupportsOutputId: supportsOutputId,
6370
}, nil
6471
}

internal/fleet/integration_policy/schema.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ func getSchemaV1() schema.Schema {
9292
Description: "The version of the integration package.",
9393
Required: true,
9494
},
95+
"output_id": schema.StringAttribute{
96+
Description: "The ID of the output to send data to. When not specified, the default output of the agent policy will be used.",
97+
Optional: true,
98+
},
9599
"vars_json": schema.StringAttribute{
96100
Description: "Integration-level variables as JSON.",
97101
CustomType: jsontypes.NormalizedType{},
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
variable "policy_name" {
2+
description = "The integration policy name"
3+
type = string
4+
}
5+
6+
variable "output_name" {
7+
description = "The output name"
8+
type = string
9+
}
10+
11+
variable "integration_name" {
12+
description = "The integration name"
13+
type = string
14+
default = "tcp"
15+
}
16+
17+
variable "integration_version" {
18+
description = "The integration version"
19+
type = string
20+
default = "1.16.0"
21+
}
22+
23+
provider "elasticstack" {
24+
elasticsearch {}
25+
kibana {}
26+
}
27+
28+
resource "elasticstack_fleet_integration" "test_policy" {
29+
name = var.integration_name
30+
version = var.integration_version
31+
force = true
32+
}
33+
34+
resource "elasticstack_fleet_agent_policy" "test_policy" {
35+
name = "${var.policy_name} Agent Policy"
36+
namespace = "default"
37+
description = "IntegrationPolicyTest Agent Policy"
38+
monitor_logs = true
39+
monitor_metrics = true
40+
skip_destroy = false
41+
}
42+
43+
resource "elasticstack_fleet_output" "test_output" {
44+
name = var.output_name
45+
output_id = "${var.policy_name}-test-output"
46+
type = "elasticsearch"
47+
config_yaml = yamlencode({
48+
"ssl.verification_mode" : "none"
49+
})
50+
default_integrations = false
51+
default_monitoring = false
52+
hosts = [
53+
"https://elasticsearch:9200"
54+
]
55+
}
56+
57+
data "elasticstack_fleet_enrollment_tokens" "test_policy" {
58+
policy_id = elasticstack_fleet_agent_policy.test_policy.policy_id
59+
}
60+
61+
resource "elasticstack_fleet_integration_policy" "test_policy" {
62+
name = var.policy_name
63+
namespace = "default"
64+
description = "IntegrationPolicyTest Policy with Output"
65+
agent_policy_id = elasticstack_fleet_agent_policy.test_policy.policy_id
66+
integration_name = elasticstack_fleet_integration.test_policy.name
67+
integration_version = elasticstack_fleet_integration.test_policy.version
68+
output_id = elasticstack_fleet_output.test_output.output_id
69+
70+
input {
71+
input_id = "tcp-tcp"
72+
enabled = true
73+
streams_json = jsonencode({
74+
"tcp.generic" : {
75+
"enabled" : true
76+
"vars" : {
77+
"listen_address" : "localhost"
78+
"listen_port" : 8080
79+
"data_stream.dataset" : "tcp.generic"
80+
"tags" : []
81+
"syslog_options" : "field: message"
82+
"ssl" : ""
83+
"custom" : ""
84+
}
85+
}
86+
})
87+
}
88+
}

0 commit comments

Comments
 (0)