Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 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 @@ -290,7 +290,7 @@ func (model tfModel) toPutIndexParams(serverFlavor string) models.PutIndexParams
Timeout: timeout,
}

if serverFlavor != "serverless" {
if serverFlavor != clients.ServerlessFlavor {
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 = ""
}

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
}
68 changes: 63 additions & 5 deletions internal/fleet/agent_policy/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet"
"github.com/elastic/terraform-provider-elasticstack/internal/fleet/agent_policy"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/elastic/terraform-provider-elasticstack/internal/versionutils"
"github.com/hashicorp/go-version"
Expand All @@ -19,7 +20,6 @@ import (
)

var minVersionAgentPolicy = version.Must(version.NewVersion("8.6.0"))
var minVersionGlobalDataTags = version.Must(version.NewVersion("8.15.0"))

func TestAccResourceAgentPolicyFromSDK(t *testing.T) {
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
Expand Down Expand Up @@ -95,6 +95,27 @@ func TestAccResourceAgentPolicy(t *testing.T) {
}),
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionAgentPolicy),
Config: testAccResourceAgentPolicyCreate(policyName, false),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyName)),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "Test Agent Policy"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "monitor_logs", "true"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "monitor_metrics", "false"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "skip_destroy", "false"),
resource.TestCheckResourceAttrWith("elasticstack_fleet_agent_policy.test_policy", "policy_id", func(value string) error {
originalPolicyId = value

if len(value) == 0 {
return errors.New("expected policy_id to be non empty")
}

return nil
}),
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionAgentPolicy),
Config: testAccResourceAgentPolicyUpdate(policyName, false),
Expand Down Expand Up @@ -123,7 +144,7 @@ func TestAccResourceAgentPolicy(t *testing.T) {
ImportStateVerifyIgnore: []string{"skip_destroy"},
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionGlobalDataTags),
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionGlobalDataTags),
Config: testAccResourceAgentPolicyCreateWithGlobalDataTags(policyNameGlobalDataTags, false),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyNameGlobalDataTags)),
Expand All @@ -137,7 +158,7 @@ func TestAccResourceAgentPolicy(t *testing.T) {
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionGlobalDataTags),
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionGlobalDataTags),
Config: testAccResourceAgentPolicyUpdateWithGlobalDataTags(policyNameGlobalDataTags, false),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Updated Policy %s", policyNameGlobalDataTags)),
Expand All @@ -151,7 +172,7 @@ func TestAccResourceAgentPolicy(t *testing.T) {
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionGlobalDataTags),
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionGlobalDataTags),
Config: testAccResourceAgentPolicyUpdateWithNoGlobalDataTags(policyNameGlobalDataTags, false),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Updated Policy %s", policyNameGlobalDataTags)),
Expand All @@ -163,6 +184,19 @@ func TestAccResourceAgentPolicy(t *testing.T) {
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.#", "0"),
),
},
{
SkipFunc: versionutils.CheckIfNotServerless(),
Config: testAccResourceAgentPolicyUpdateWithSupportsAgentless(policyNameGlobalDataTags, false),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Updated Policy %s", policyNameGlobalDataTags)),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "namespace", "default"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "description", "This policy was updated with supports_agentless"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "monitor_logs", "false"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "monitor_metrics", "true"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "skip_destroy", "false"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "supports_agentless", "true"),
),
},
},
})
}
Expand Down Expand Up @@ -199,7 +233,7 @@ func TestAccResourceAgentPolicyWithBadGlobalDataTags(t *testing.T) {
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionGlobalDataTags),
SkipFunc: versionutils.CheckIfVersionIsUnsupported(agent_policy.MinVersionGlobalDataTags),
Config: testAccResourceAgentPolicyCreateWithBadGlobalDataTags(policyName, true),
ExpectError: regexp.MustCompile(".*Error: Invalid Attribute Combination.*"),
},
Expand Down Expand Up @@ -229,6 +263,7 @@ data "elasticstack_fleet_enrollment_tokens" "test_policy" {

`, fmt.Sprintf("Policy %s", id), skipDestroy)
}

func testAccResourceAgentPolicyCreateWithGlobalDataTags(id string, skipDestroy bool) string {
return fmt.Sprintf(`
provider "elasticstack" {
Expand Down Expand Up @@ -337,6 +372,29 @@ data "elasticstack_fleet_enrollment_tokens" "test_policy" {
`, fmt.Sprintf("Updated Policy %s", id), skipDestroy)
}

func testAccResourceAgentPolicyUpdateWithSupportsAgentless(id string, skipDestroy bool) string {
return fmt.Sprintf(`
provider "elasticstack" {
elasticsearch {}
kibana {}
}

resource "elasticstack_fleet_agent_policy" "test_policy" {
name = "%s"
namespace = "default"
description = "This policy was updated with supports_agentless"
monitor_logs = false
monitor_metrics = true
skip_destroy = %t
supports_agentless = true
}

data "elasticstack_fleet_enrollment_tokens" "test_policy" {
policy_id = elasticstack_fleet_agent_policy.test_policy.policy_id
}
`, fmt.Sprintf("Updated Policy %s", id), skipDestroy)
}

func testAccResourceAgentPolicyUpdate(id string, skipDestroy bool) string {
return fmt.Sprintf(`
provider "elasticstack" {
Expand Down
Loading
Loading