Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
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, Deprecated) 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
805 changes: 738 additions & 67 deletions generated/kbapi/kibana.gen.go

Large diffs are not rendered by default.

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
56 changes: 53 additions & 3 deletions internal/fleet/integration_policy/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,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 +50,38 @@ 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 := !model.AgentPolicyID.IsNull() && !model.AgentPolicyID.IsUnknown()
originallyUsedAgentPolicyIDs := !model.AgentPolicyIDs.IsNull() && !model.AgentPolicyIDs.IsUnknown()

if originallyUsedAgentPolicyID && !originallyUsedAgentPolicyIDs {
// Only set agent_policy_id if it was originally used
model.AgentPolicyID = types.StringPointerValue(data.PolicyId)
} else if originallyUsedAgentPolicyIDs && !originallyUsedAgentPolicyID {
// Only set agent_policy_ids if it was originally used
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)
}
} else {
// 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 {
// 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,7 +117,7 @@ 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

body := kbapi.PackagePolicyRequest{
Expand All @@ -94,7 +130,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
}
141 changes: 141 additions & 0 deletions internal/fleet/integration_policy/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,54 @@ import (
)

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

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 TestAccResourceIntegrationPolicyBothAgentPolicyFields(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: testAccResourceIntegrationPolicyCreateWithBothAgentPolicyFields(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.TestCheckResourceAttrSet("elasticstack_fleet_integration_policy.test_policy", "agent_policy_id"),
resource.TestCheckResourceAttr("elasticstack_fleet_integration_policy.test_policy", "agent_policy_ids.#", "1"),
),
},
},
})
}

func TestJsonTypes(t *testing.T) {
mapBytes, err := json.Marshal(map[string]string{})
Expand Down Expand Up @@ -451,3 +499,96 @@ 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 testAccResourceIntegrationPolicyCreateWithBothAgentPolicyFields(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" {
name = "%s Agent Policy"
namespace = "default"
description = "IntegrationPolicyTest Agent Policy"
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_id = elasticstack_fleet_agent_policy.test_policy.policy_id
agent_policy_ids = [elasticstack_fleet_agent_policy.test_policy.policy_id]
integration_name = elasticstack_fleet_integration.test_policy.name
integration_version = elasticstack_fleet_integration.test_policy.version
input {
input_id = "tcp-tcp"
enabled = true
}
}
`, id, id)
}
11 changes: 9 additions & 2 deletions internal/fleet/integration_policy/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"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/types"
)

//go:embed resource-description.md
Expand Down Expand Up @@ -50,8 +51,14 @@ func getSchemaV1() schema.Schema {
Required: true,
},
"agent_policy_id": schema.StringAttribute{
Description: "ID of the agent policy.",
Required: true,
Description: "ID of the agent policy.",
DeprecationMessage: "Use agent_policy_ids instead. This field will be removed in a future version.",
Optional: true,
},
"agent_policy_ids": schema.ListAttribute{
Description: "List of agent policy IDs.",
ElementType: types.StringType,
Optional: true,
},
"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
Loading