-
Notifications
You must be signed in to change notification settings - Fork 122
[Feature] Introduce elasticsearch alias resource with atomic write index management and nested attributes #1343
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
base: main
Are you sure you want to change the base?
Changes from 7 commits
91ab7d6
5bf740c
7f5a743
8bb4289
5c90036
efbb995
c95cd5c
dbf477e
dd27d0a
0bec889
8a64b68
858e669
92ee168
cdead36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
|
||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "elasticstack_elasticsearch_alias Resource - terraform-provider-elasticstack" | ||
subcategory: "Elasticsearch" | ||
description: |- | ||
Manages an Elasticsearch alias. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html | ||
--- | ||
|
||
# elasticstack_elasticsearch_alias (Resource) | ||
|
||
Manages an Elasticsearch alias. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html | ||
|
||
|
||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `name` (String) The alias name. | ||
|
||
### Optional | ||
|
||
- `read_indices` (Block Set) Set of read indices for the alias. (see [below for nested schema](#nestedblock--read_indices)) | ||
- `write_index` (Block, Optional) The write index for the alias. Only one write index is allowed per alias. (see [below for nested schema](#nestedblock--write_index)) | ||
|
||
### Read-Only | ||
|
||
- `id` (String) Generated ID of the alias resource. | ||
|
||
<a id="nestedblock--read_indices"></a> | ||
### Nested Schema for `read_indices` | ||
|
||
Required: | ||
|
||
- `name` (String) Name of the read index. | ||
|
||
Optional: | ||
|
||
- `filter` (String) Query used to limit documents the alias can access. | ||
- `index_routing` (String) Value used to route indexing operations to a specific shard. | ||
- `is_hidden` (Boolean) If true, the alias is hidden. | ||
- `routing` (String) Value used to route indexing and search operations to a specific shard. | ||
- `search_routing` (String) Value used to route search operations to a specific shard. | ||
|
||
|
||
<a id="nestedblock--write_index"></a> | ||
### Nested Schema for `write_index` | ||
|
||
Required: | ||
|
||
- `name` (String) Name of the write index. | ||
|
||
Optional: | ||
|
||
- `filter` (String) Query used to limit documents the alias can access. | ||
- `index_routing` (String) Value used to route indexing operations to a specific shard. | ||
- `is_hidden` (Boolean) If true, the alias is hidden. | ||
- `routing` (String) Value used to route indexing and search operations to a specific shard. | ||
- `search_routing` (String) Value used to route search operations to a specific shard. |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -581,6 +581,246 @@ func DeleteDataStreamLifecycle(ctx context.Context, apiClient *clients.ApiClient | |||||||
return nil | ||||||||
} | ||||||||
|
||||||||
func GetAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string) (map[string]models.Index, fwdiags.Diagnostics) { | ||||||||
esClient, err := apiClient.GetESClient() | ||||||||
if err != nil { | ||||||||
return nil, fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
res, err := esClient.Indices.GetAlias( | ||||||||
esClient.Indices.GetAlias.WithName(aliasName), | ||||||||
esClient.Indices.GetAlias.WithContext(ctx), | ||||||||
) | ||||||||
if err != nil { | ||||||||
return nil, fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
defer res.Body.Close() | ||||||||
|
||||||||
if res.StatusCode == http.StatusNotFound { | ||||||||
return nil, nil | ||||||||
} | ||||||||
|
||||||||
diags := diagutil.CheckError(res, fmt.Sprintf("Unable to get alias '%s'", aliasName)) | ||||||||
if diagutil.FrameworkDiagsFromSDK(diags).HasError() { | ||||||||
return nil, diagutil.FrameworkDiagsFromSDK(diags) | ||||||||
} | ||||||||
|
||||||||
indices := make(map[string]models.Index) | ||||||||
if err := json.NewDecoder(res.Body).Decode(&indices); err != nil { | ||||||||
return nil, fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
return indices, nil | ||||||||
} | ||||||||
|
||||||||
func PutAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string, indices []string, alias *models.IndexAlias) fwdiags.Diagnostics { | ||||||||
|
||||||||
esClient, err := apiClient.GetESClient() | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
// Build the request body for index aliases API | ||||||||
var actions []map[string]interface{} | ||||||||
|
||||||||
for _, index := range indices { | ||||||||
addAction := map[string]interface{}{ | ||||||||
"add": map[string]interface{}{ | ||||||||
"index": index, | ||||||||
"alias": aliasName, | ||||||||
}, | ||||||||
} | ||||||||
|
||||||||
// Only include non-empty optional fields in the add action | ||||||||
addActionDetails := addAction["add"].(map[string]interface{}) | ||||||||
|
||||||||
if alias.Filter != nil { | ||||||||
addActionDetails["filter"] = alias.Filter | ||||||||
} | ||||||||
if alias.IndexRouting != "" { | ||||||||
addActionDetails["index_routing"] = alias.IndexRouting | ||||||||
} | ||||||||
if alias.SearchRouting != "" { | ||||||||
addActionDetails["search_routing"] = alias.SearchRouting | ||||||||
} | ||||||||
if alias.Routing != "" { | ||||||||
addActionDetails["routing"] = alias.Routing | ||||||||
} | ||||||||
if alias.IsHidden { | ||||||||
addActionDetails["is_hidden"] = alias.IsHidden | ||||||||
} | ||||||||
if alias.IsWriteIndex { | ||||||||
addActionDetails["is_write_index"] = alias.IsWriteIndex | ||||||||
} | ||||||||
|
||||||||
actions = append(actions, addAction) | ||||||||
} | ||||||||
|
||||||||
aliasActions := map[string]interface{}{ | ||||||||
"actions": actions, | ||||||||
} | ||||||||
|
||||||||
aliasBytes, err := json.Marshal(aliasActions) | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
res, err := esClient.Indices.UpdateAliases( | ||||||||
bytes.NewReader(aliasBytes), | ||||||||
esClient.Indices.UpdateAliases.WithContext(ctx), | ||||||||
) | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
defer res.Body.Close() | ||||||||
|
||||||||
diags := diagutil.CheckError(res, fmt.Sprintf("Unable to create/update alias '%s'", aliasName)) | ||||||||
return diagutil.FrameworkDiagsFromSDK(diags) | ||||||||
} | ||||||||
|
||||||||
func DeleteAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string, indices []string) fwdiags.Diagnostics { | ||||||||
|
||||||||
esClient, err := apiClient.GetESClient() | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
// Use UpdateAliases API for deletion to handle multiple indices | ||||||||
aliasActions := map[string]interface{}{ | ||||||||
"actions": []map[string]interface{}{ | ||||||||
{ | ||||||||
"remove": map[string]interface{}{ | ||||||||
"indices": indices, | ||||||||
"alias": aliasName, | ||||||||
}, | ||||||||
}, | ||||||||
}, | ||||||||
} | ||||||||
|
||||||||
aliasBytes, err := json.Marshal(aliasActions) | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
res, err := esClient.Indices.UpdateAliases( | ||||||||
bytes.NewReader(aliasBytes), | ||||||||
esClient.Indices.UpdateAliases.WithContext(ctx), | ||||||||
) | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
defer res.Body.Close() | ||||||||
|
||||||||
diags := diagutil.CheckError(res, fmt.Sprintf("Unable to delete alias '%s'", aliasName)) | ||||||||
return diagutil.FrameworkDiagsFromSDK(diags) | ||||||||
} | ||||||||
|
||||||||
// AliasAction represents a single action in an atomic alias update operation | ||||||||
type AliasAction struct { | ||||||||
Type string // "add" or "remove" | ||||||||
Index string | ||||||||
Alias string | ||||||||
IsWriteIndex bool | ||||||||
Filter map[string]interface{} | ||||||||
IndexRouting string | ||||||||
IsHidden bool | ||||||||
Routing string | ||||||||
SearchRouting string | ||||||||
} | ||||||||
|
||||||||
// UpdateAliasesAtomic performs atomic alias updates using multiple actions | ||||||||
func UpdateAliasesAtomic(ctx context.Context, apiClient *clients.ApiClient, actions []AliasAction) fwdiags.Diagnostics { | ||||||||
esClient, err := apiClient.GetESClient() | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
var aliasActions []map[string]interface{} | ||||||||
|
||||||||
for _, action := range actions { | ||||||||
switch action.Type { | ||||||||
case "remove": | ||||||||
aliasActions = append(aliasActions, map[string]interface{}{ | ||||||||
"remove": map[string]interface{}{ | ||||||||
"index": action.Index, | ||||||||
"alias": action.Alias, | ||||||||
}, | ||||||||
}) | ||||||||
case "add": | ||||||||
addDetails := map[string]interface{}{ | ||||||||
"index": action.Index, | ||||||||
"alias": action.Alias, | ||||||||
} | ||||||||
|
||||||||
if action.IsWriteIndex { | ||||||||
addDetails["is_write_index"] = true | ||||||||
} | ||||||||
if action.Filter != nil { | ||||||||
addDetails["filter"] = action.Filter | ||||||||
} | ||||||||
if action.IndexRouting != "" { | ||||||||
addDetails["index_routing"] = action.IndexRouting | ||||||||
} | ||||||||
if action.SearchRouting != "" { | ||||||||
addDetails["search_routing"] = action.SearchRouting | ||||||||
} | ||||||||
if action.Routing != "" { | ||||||||
addDetails["routing"] = action.Routing | ||||||||
} | ||||||||
if action.IsHidden { | ||||||||
addDetails["is_hidden"] = action.IsHidden | ||||||||
} | ||||||||
|
||||||||
aliasActions = append(aliasActions, map[string]interface{}{ | ||||||||
"add": addDetails, | ||||||||
}) | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
requestBody := map[string]interface{}{ | ||||||||
"actions": aliasActions, | ||||||||
} | ||||||||
|
||||||||
aliasBytes, err := json.Marshal(requestBody) | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
res, err := esClient.Indices.UpdateAliases( | ||||||||
bytes.NewReader(aliasBytes), | ||||||||
esClient.Indices.UpdateAliases.WithContext(ctx), | ||||||||
) | ||||||||
if err != nil { | ||||||||
return fwdiags.Diagnostics{ | ||||||||
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()), | ||||||||
} | ||||||||
} | ||||||||
defer res.Body.Close() | ||||||||
|
||||||||
diags := diagutil.CheckError(res, "Unable to update aliases atomically") | ||||||||
return diagutil.FrameworkDiagsFromSDK(diags) | ||||||||
|
diags := diagutil.CheckError(res, "Unable to update aliases atomically") | |
return diagutil.FrameworkDiagsFromSDK(diags) | |
return diagutil.CheckErrorFromFW(res, "Unable to update aliases atomically") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 3c8a8b2. Now using CheckErrorFromFW
directly as suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CheckErrorFromFW
will return the right type directly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 3c8a8b2. Updated to use
CheckErrorFromFW
directly as suggested.