Skip to content
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

Add support for global data tags in agent policy creation and update #730

Closed
wants to merge 13 commits into from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Add support for data_stream `lifecycle` template settings ([#724](https://github.com/elastic/terraform-provider-elasticstack/pull/724))
- Fix a provider panic when `elasticstack_kibana_action_connector` reads a non-existant connector ([#729](https://github.com/elastic/terraform-provider-elasticstack/pull/729))
- Add support for `remote_indicies` to `elasticstack_elasticsearch_security_role` & `elasticstack_kibana_security_role` (#723)[https://github.com/elastic/terraform-provider-elasticstack/pull/723]
- Added support for global data tags in agent policy creation and update. Upgraded the generated Fleet API to version 8.15.0. (#730)[https://github.com/elastic/terraform-provider-elasticstack/pull/730]
- Fix error handling in `elasticstack_kibana_import_saved_objects` ([#738](https://github.com/elastic/terraform-provider-elasticstack/pull/738))

## [0.11.6] - 2024-08-20
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 @@ -41,6 +41,7 @@ resource "elasticstack_fleet_agent_policy" "test_policy" {
- `description` (String) The description of the agent policy.
- `download_source_id` (String) The identifier for the Elastic Agent binary download server.
- `fleet_server_host_id` (String) The identifier for the Fleet server host.
- `global_data_tags` (Map of String) User-defined data tags that are added to all inputs.
- `monitor_logs` (Boolean) Enable collection of agent logs.
- `monitor_metrics` (Boolean) Enable collection of agent metrics.
- `monitoring_output_id` (String) The identifier for monitoring output.
Expand Down
4 changes: 2 additions & 2 deletions generated/alerting/api_alerting_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion generated/connectors/connectors.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

611 changes: 522 additions & 89 deletions generated/fleet/fleet.gen.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions generated/fleet/getschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func transformOutputTypeRequired(schema *Schema) {
"schemas.output_update_request_elasticsearch.required",
"schemas.output_update_request_kafka.required",
"schemas.output_update_request_logstash.required",
"schemas.output_create_request_remote_elasticsearch.required",
}

for _, v := range path {
Expand Down
117 changes: 117 additions & 0 deletions internal/fleet/agent_policy_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package fleet

import (
"context"
"fmt"

fleetapi "github.com/elastic/terraform-provider-elasticstack/generated/fleet"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -15,6 +18,8 @@ const (
monitorMetrics = "metrics"
)

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

func ResourceAgentPolicy() *schema.Resource {
agentPolicySchema := map[string]*schema.Schema{
"policy_id": {
Expand Down Expand Up @@ -79,6 +84,14 @@ func ResourceAgentPolicy() *schema.Resource {
Type: schema.TypeBool,
Optional: true,
},
"global_data_tags": {
Description: "User-defined data tags that are added to all inputs.",
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
}

return &schema.Resource{
Expand All @@ -103,6 +116,16 @@ func resourceAgentPolicyCreate(ctx context.Context, d *schema.ResourceData, meta
return diags
}

apiClient, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}

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

if id := d.Get("policy_id").(string); id != "" {
d.SetId(id)
}
Expand Down Expand Up @@ -140,6 +163,35 @@ func resourceAgentPolicyCreate(ctx context.Context, d *schema.ResourceData, meta
}
req.MonitoringEnabled = &monitoringValues

if tags, ok := d.GetOk("global_data_tags"); ok {
tagMap := tags.(map[string]interface{})

if len(tagMap) > 0 && serverVersion.LessThan(minVersionGlobalDataTags) {
return diag.FromErr(fmt.Errorf("'global_data_tags' is supported only for Elasticsearch v%s and above", minVersionGlobalDataTags.String()))
}

gdt := []map[string]fleetapi.AgentPolicyCreateRequest_GlobalDataTags_AdditionalProperties{}
for key, value := range tagMap {
name := fleetapi.AgentPolicyCreateRequest_GlobalDataTags_AdditionalProperties{}
err := name.FromAgentPolicyCreateRequestGlobalDataTags0(key)
if err != nil {
return diag.FromErr(fmt.Errorf("failed to parse global_data_tags key [%s]: %w", key, err))
}

val := fleetapi.AgentPolicyCreateRequest_GlobalDataTags_AdditionalProperties{}
err = val.FromAgentPolicyCreateRequestGlobalDataTags0(value.(string))
if err != nil {
return diag.FromErr(fmt.Errorf("failed to parse global_data_tags value [%s]: %w", value.(string), err))
}

gdt = append(gdt, map[string]fleetapi.AgentPolicyCreateRequest_GlobalDataTags_AdditionalProperties{
"name": name,
"value": val,
})
}
req.GlobalDataTags = &gdt
}

policy, diags := fleet.CreateAgentPolicy(ctx, fleetClient, req)
if diags.HasError() {
return diags
Expand All @@ -159,6 +211,16 @@ func resourceAgentPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta
return diags
}

apiClient, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}

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

req := fleetapi.AgentPolicyUpdateRequest{
Name: d.Get("name").(string),
Namespace: d.Get("namespace").(string),
Expand Down Expand Up @@ -189,6 +251,36 @@ func resourceAgentPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta
}
req.MonitoringEnabled = &monitoringValues

if tags, ok := d.GetOk("global_data_tags"); ok {
tagMap := tags.(map[string]interface{})

if len(tagMap) > 0 && serverVersion.LessThan(minVersionGlobalDataTags) {
return diag.FromErr(fmt.Errorf("'global_data_tags' is supported only for Elasticsearch v%s and above", minVersionGlobalDataTags.String()))
}

globalDataTags := []map[string]fleetapi.AgentPolicyUpdateRequest_GlobalDataTags_AdditionalProperties{}
for key, val := range tagMap {
var name, value fleetapi.AgentPolicyUpdateRequest_GlobalDataTags_AdditionalProperties
err := name.FromAgentPolicyUpdateRequestGlobalDataTags0(key)
if err != nil {
return diag.FromErr(err)
}

err = value.FromAgentPolicyUpdateRequestGlobalDataTags0(val.(string))
if err != nil {
return diag.FromErr(err)
}

globalDataTags = append(globalDataTags, map[string]fleetapi.AgentPolicyUpdateRequest_GlobalDataTags_AdditionalProperties{
"name": name,
"value": value,
})
}
req.GlobalDataTags = &globalDataTags
} else {
req.GlobalDataTags = &[]map[string]fleetapi.AgentPolicyUpdateRequest_GlobalDataTags_AdditionalProperties{} // Ensure it's an empty array
}

_, diags = fleet.UpdateAgentPolicy(ctx, fleetClient, d.Id(), req)
if diags.HasError() {
return diags
Expand Down Expand Up @@ -264,6 +356,31 @@ func resourceAgentPolicyRead(ctx context.Context, d *schema.ResourceData, meta i
}
}

if agentPolicy.GlobalDataTags != nil {

globalDataTags := make(map[string]string, len(*agentPolicy.GlobalDataTags))
for _, tag := range *agentPolicy.GlobalDataTags {
name, err := tag["name"].AsAgentPolicyGlobalDataTags0()
if err != nil {
return diag.FromErr(err)
}

value, err := tag["value"].AsAgentPolicyGlobalDataTags0()
if err != nil {
return diag.FromErr(err)
}
globalDataTags[name] = value
}

if err := d.Set("global_data_tags", globalDataTags); err != nil {
return diag.FromErr(err)
}
} else {
if err := d.Set("global_data_tags", nil); err != nil {
return diag.FromErr(err)
}
}

return nil
}

Expand Down
123 changes: 123 additions & 0 deletions internal/fleet/agent_policy_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import (
)

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

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

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
Expand Down Expand Up @@ -50,6 +52,51 @@ func TestAccResourceAgentPolicy(t *testing.T) {
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "skip_destroy", "false"),
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionGlobalDataTags),
Config: testAccResourceAgentPolicyCreateWithGlobalDataTags(policyNameGlobalDataTags, false),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "name", fmt.Sprintf("Policy %s", policyNameGlobalDataTags)),
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.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.tag1", "value1"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.tag2", "value2"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.tag3", "value3"),
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionGlobalDataTags),
Config: testAccResourceAgentPolicyUpdateWithGlobalDataTags(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"),
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", "global_data_tags.tag1", "value1a"),
resource.TestCheckResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.tag2", "value2b"),
resource.TestCheckNoResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.tag3"),
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionGlobalDataTags),
Config: testAccResourceAgentPolicyUpdateWithNoGlobalDataTags(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 without global data tags"),
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.TestCheckNoResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.tag1"),
resource.TestCheckNoResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.tag2"),
resource.TestCheckNoResourceAttr("elasticstack_fleet_agent_policy.test_policy", "global_data_tags.tag3"),
),
},
},
})
}
Expand Down Expand Up @@ -123,6 +170,82 @@ data "elasticstack_fleet_enrollment_tokens" "test_policy" {
`, fmt.Sprintf("Updated Policy %s", id), skipDestroy)
}

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

resource "elasticstack_fleet_agent_policy" "test_policy" {
name = "%s"
namespace = "default"
description = "Test Agent Policy"
monitor_logs = true
monitor_metrics = false
skip_destroy = %t
global_data_tags = {
tag1 = "value1"
tag2 = "value2"
tag3 = "value3"
}
}

data "elasticstack_fleet_enrollment_tokens" "test_policy" {
policy_id = elasticstack_fleet_agent_policy.test_policy.policy_id
}

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

func testAccResourceAgentPolicyUpdateWithGlobalDataTags(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"
monitor_logs = false
monitor_metrics = true
skip_destroy = %t
global_data_tags = {
tag1 = "value1a"
tag2 = "value2b"
}
}

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 testAccResourceAgentPolicyUpdateWithNoGlobalDataTags(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 without global data tags"
monitor_logs = false
monitor_metrics = true
skip_destroy = %t
}

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 checkResourceAgentPolicyDestroy(s *terraform.State) error {
client, err := clients.NewAcceptanceTestingClient()
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions internal/fleet/integration_policy_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

fleetapi "github.com/elastic/terraform-provider-elasticstack/generated/fleet"
"github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
)

func ResourceIntegrationPolicy() *schema.Resource {
Expand Down Expand Up @@ -135,7 +136,7 @@ func resourceIntegrationPolicyCreate(ctx context.Context, d *schema.ResourceData
}

req := fleetapi.CreatePackagePolicyJSONRequestBody{
PolicyId: d.Get("agent_policy_id").(string),
PolicyId: utils.Pointer(d.Get("agent_policy_id").(string)),
Name: d.Get("name").(string),
}
req.Package.Name = d.Get("integration_name").(string)
Expand Down Expand Up @@ -215,7 +216,7 @@ func resourceIntegrationPolicyUpdate(ctx context.Context, d *schema.ResourceData
}

req := fleetapi.UpdatePackagePolicyJSONRequestBody{
PolicyId: d.Get("agent_policy_id").(string),
PolicyId: utils.Pointer(d.Get("agent_policy_id").(string)),
Name: d.Get("name").(string),
}
req.Package.Name = d.Get("integration_name").(string)
Expand Down
Loading
Loading