Skip to content

Commit e00b2f3

Browse files
CopilottobiodimuonCopilot
authored
[Feature] Introduce elasticsearch alias resource with atomic write index management and nested attributes (#1343)
* Initial plan * Implement core alias resource structure and API functions Co-authored-by: tobio <[email protected]> * Fix null value handling and data stream alias test passing Co-authored-by: tobio <[email protected]> * Alias resource acceptance tests now passing Co-authored-by: tobio <[email protected]> * Complete alias resource implementation with linting and documentation Co-authored-by: tobio <[email protected]> * Major schema redesign and improved acceptance tests Co-authored-by: tobio <[email protected]> * Final fixes: correct test configurations and linting cleanup Co-authored-by: tobio <[email protected]> * Fix acceptance tests with variable declarations and direct index creation Co-authored-by: tobio <[email protected]> * Fix linting issues: remove unused constants and fix staticcheck warnings Co-authored-by: tobio <[email protected]> * Convert schema from blocks to nested attributes Co-authored-by: tobio <[email protected]> * Address all PR feedback: refactor models, clean API functions, improve validation Co-authored-by: tobio <[email protected]> * Tidy up * Support externally managed aliases in the index resource * Only add aliases that require an update * Restructure tests * Use existing util * Update internal/clients/elasticsearch/index.go Co-authored-by: Copilot <[email protected]> * Revert "Update internal/clients/elasticsearch/index.go" This reverts commit c080c0d. * Changelog * Changelog --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: tobio <[email protected]> Co-authored-by: Toby Brain <[email protected]> Co-authored-by: Dmitry Onishchenko <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Dmitry Onishchenko <[email protected]> Co-authored-by: Toby Brain <[email protected]>
1 parent 15e62bf commit e00b2f3

File tree

27 files changed

+1907
-95
lines changed

27 files changed

+1907
-95
lines changed

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,48 @@
11
## [Unreleased]
22

3+
### Breaking changes
4+
5+
#### `elasticstack_elasticsearch_index.alias` block has changed to a set attribute.
6+
7+
The `alias` block in the `elasticstack_elasticsearch_index` resource has been moved to an attribute.
8+
This transition provides better support for future changes in both the provider and the underlying Terraform framework.
9+
10+
Existing usage of the `alias` block must be migrated to the attribute syntax. For example:
11+
12+
```hcl
13+
alias {
14+
name = "my_alias_1"
15+
}
16+
17+
alias {
18+
name = "my_alias_2"
19+
filter = jsonencode({
20+
term = { "user.id" = "developer" }
21+
})
22+
}
23+
```
24+
25+
becomes
26+
27+
```hcl
28+
alias = [
29+
{
30+
name = "my_alias_1"
31+
},
32+
{
33+
name = "my_alias_2"
34+
filter = jsonencode({
35+
term = { "user.id" = "developer" }
36+
})
37+
}
38+
]
39+
```
40+
41+
### Changes
42+
343
- Support `.bedrock` and `.gen-ai` connectors ([#1467](https://github.com/elastic/terraform-provider-elasticstack/pull/1467))
444
- Support the `solution` attribute in `elasticstack_kibana_space` from 8.16 ([#1486](https://github.com/elastic/terraform-provider-elasticstack/pull/1486))
45+
- Add `elasticstack_elasticsearch_alias` resource ([#1343](https://github.com/elastic/terraform-provider-elasticstack/pull/1343))
546

647
## [0.12.2] - 2025-11-19
748
- Fix `elasticstack_elasticsearch_snapshot_lifecycle` metadata type conversion causing terraform apply to fail ([#1409](https://github.com/elastic/terraform-provider-elasticstack/issues/1409))

docs/resources/elasticsearch_index.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,14 @@ provider "elasticstack" {
2020
resource "elasticstack_elasticsearch_index" "my_index" {
2121
name = "my-index"
2222
23-
alias {
23+
alias = [{
2424
name = "my_alias_1"
25-
}
26-
27-
alias {
25+
}, {
2826
name = "my_alias_2"
2927
filter = jsonencode({
3028
term = { "user.id" = "developer" }
3129
})
32-
}
30+
}]
3331
3432
mappings = jsonencode({
3533
properties = {
@@ -59,7 +57,7 @@ resource "elasticstack_elasticsearch_index" "my_index" {
5957

6058
### Optional
6159

62-
- `alias` (Block Set) Aliases for the index. (see [below for nested schema](#nestedblock--alias))
60+
- `alias` (Attributes Set) Aliases for the index. (see [below for nested schema](#nestedatt--alias))
6361
- `analysis_analyzer` (String) A JSON string describing the analyzers applied to the index.
6462
- `analysis_char_filter` (String) A JSON string describing the char_filters applied to the index.
6563
- `analysis_filter` (String) A JSON string describing the filters applied to the index.
@@ -135,7 +133,7 @@ resource "elasticstack_elasticsearch_index" "my_index" {
135133
- `id` (String) Internal identifier of the resource
136134
- `settings_raw` (String) All raw settings fetched from the cluster.
137135

138-
<a id="nestedblock--alias"></a>
136+
<a id="nestedatt--alias"></a>
139137
### Nested Schema for `alias`
140138

141139
Required:
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "elasticstack_elasticsearch_index_alias Resource - terraform-provider-elasticstack"
4+
subcategory: "Index"
5+
description: |-
6+
Manages an Elasticsearch alias. See the alias documentation https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html for more details.
7+
---
8+
9+
# elasticstack_elasticsearch_index_alias (Resource)
10+
11+
Manages an Elasticsearch alias. See the [alias documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html) for more details.
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `name` (String) The alias name.
21+
22+
### Optional
23+
24+
- `read_indices` (Attributes Set) Set of read indices for the alias. (see [below for nested schema](#nestedatt--read_indices))
25+
- `write_index` (Attributes) The write index for the alias. Only one write index is allowed per alias. (see [below for nested schema](#nestedatt--write_index))
26+
27+
### Read-Only
28+
29+
- `id` (String) Generated ID of the alias resource.
30+
31+
<a id="nestedatt--read_indices"></a>
32+
### Nested Schema for `read_indices`
33+
34+
Required:
35+
36+
- `name` (String) Name of the read index.
37+
38+
Optional:
39+
40+
- `filter` (String) Query used to limit documents the alias can access.
41+
- `index_routing` (String) Value used to route indexing operations to a specific shard.
42+
- `is_hidden` (Boolean) If true, the alias is hidden.
43+
- `routing` (String) Value used to route indexing and search operations to a specific shard.
44+
- `search_routing` (String) Value used to route search operations to a specific shard.
45+
46+
47+
<a id="nestedatt--write_index"></a>
48+
### Nested Schema for `write_index`
49+
50+
Required:
51+
52+
- `name` (String) Name of the write index.
53+
54+
Optional:
55+
56+
- `filter` (String) Query used to limit documents the alias can access.
57+
- `index_routing` (String) Value used to route indexing operations to a specific shard.
58+
- `is_hidden` (Boolean) If true, the alias is hidden.
59+
- `routing` (String) Value used to route indexing and search operations to a specific shard.
60+
- `search_routing` (String) Value used to route search operations to a specific shard.

examples/resources/elasticstack_elasticsearch_index/resource.tf

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ provider "elasticstack" {
55
resource "elasticstack_elasticsearch_index" "my_index" {
66
name = "my-index"
77

8-
alias {
8+
alias = [{
99
name = "my_alias_1"
10-
}
11-
12-
alias {
10+
}, {
1311
name = "my_alias_2"
1412
filter = jsonencode({
1513
term = { "user.id" = "developer" }
1614
})
17-
}
15+
}]
1816

1917
mappings = jsonencode({
2018
properties = {

internal/clients/elasticsearch/index.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,133 @@ func DeleteDataStreamLifecycle(ctx context.Context, apiClient *clients.ApiClient
581581
return nil
582582
}
583583

584+
func GetAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string) (map[string]models.Index, fwdiags.Diagnostics) {
585+
esClient, err := apiClient.GetESClient()
586+
if err != nil {
587+
return nil, fwdiags.Diagnostics{
588+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
589+
}
590+
}
591+
592+
res, err := esClient.Indices.GetAlias(
593+
esClient.Indices.GetAlias.WithName(aliasName),
594+
esClient.Indices.GetAlias.WithContext(ctx),
595+
)
596+
if err != nil {
597+
return nil, fwdiags.Diagnostics{
598+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
599+
}
600+
}
601+
defer res.Body.Close()
602+
603+
if res.StatusCode == http.StatusNotFound {
604+
return nil, nil
605+
}
606+
607+
diags := diagutil.CheckErrorFromFW(res, fmt.Sprintf("Unable to get alias '%s'", aliasName))
608+
if diags.HasError() {
609+
return nil, diags
610+
}
611+
612+
indices := make(map[string]models.Index)
613+
if err := json.NewDecoder(res.Body).Decode(&indices); err != nil {
614+
return nil, fwdiags.Diagnostics{
615+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
616+
}
617+
}
618+
619+
return indices, nil
620+
}
621+
622+
// AliasAction represents a single action in an atomic alias update operation
623+
type AliasAction struct {
624+
Type string // "add" or "remove"
625+
Index string
626+
Alias string
627+
IsWriteIndex bool
628+
Filter map[string]interface{}
629+
IndexRouting string
630+
IsHidden bool
631+
Routing string
632+
SearchRouting string
633+
}
634+
635+
// UpdateAliasesAtomic performs atomic alias updates using multiple actions
636+
func UpdateAliasesAtomic(ctx context.Context, apiClient *clients.ApiClient, actions []AliasAction) fwdiags.Diagnostics {
637+
esClient, err := apiClient.GetESClient()
638+
if err != nil {
639+
return fwdiags.Diagnostics{
640+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
641+
}
642+
}
643+
644+
var aliasActions []map[string]interface{}
645+
646+
for _, action := range actions {
647+
switch action.Type {
648+
case "remove":
649+
aliasActions = append(aliasActions, map[string]interface{}{
650+
"remove": map[string]interface{}{
651+
"index": action.Index,
652+
"alias": action.Alias,
653+
},
654+
})
655+
case "add":
656+
addDetails := map[string]interface{}{
657+
"index": action.Index,
658+
"alias": action.Alias,
659+
}
660+
661+
if action.IsWriteIndex {
662+
addDetails["is_write_index"] = true
663+
}
664+
if action.Filter != nil {
665+
addDetails["filter"] = action.Filter
666+
}
667+
if action.IndexRouting != "" {
668+
addDetails["index_routing"] = action.IndexRouting
669+
}
670+
if action.SearchRouting != "" {
671+
addDetails["search_routing"] = action.SearchRouting
672+
}
673+
if action.Routing != "" {
674+
addDetails["routing"] = action.Routing
675+
}
676+
if action.IsHidden {
677+
addDetails["is_hidden"] = action.IsHidden
678+
}
679+
680+
aliasActions = append(aliasActions, map[string]interface{}{
681+
"add": addDetails,
682+
})
683+
}
684+
}
685+
686+
requestBody := map[string]interface{}{
687+
"actions": aliasActions,
688+
}
689+
690+
aliasBytes, err := json.Marshal(requestBody)
691+
if err != nil {
692+
return fwdiags.Diagnostics{
693+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
694+
}
695+
}
696+
697+
res, err := esClient.Indices.UpdateAliases(
698+
bytes.NewReader(aliasBytes),
699+
esClient.Indices.UpdateAliases.WithContext(ctx),
700+
)
701+
if err != nil {
702+
return fwdiags.Diagnostics{
703+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
704+
}
705+
}
706+
defer res.Body.Close()
707+
708+
return diagutil.CheckErrorFromFW(res, "Unable to update aliases atomically")
709+
}
710+
584711
func PutIngestPipeline(ctx context.Context, apiClient *clients.ApiClient, pipeline *models.IngestPipeline) diag.Diagnostics {
585712
var diags diag.Diagnostics
586713
pipelineBytes, err := json.Marshal(pipeline)

0 commit comments

Comments
 (0)