Skip to content

Commit

Permalink
Migrate index resource to plugin framework (#698)
Browse files Browse the repository at this point in the history
* Migrate the index resource to the tf framework

* Docs

* Mapping replacement

* Fixup build

* PR feedback
  • Loading branch information
tobio authored Sep 11, 2024
1 parent ca13c73 commit de65ccf
Show file tree
Hide file tree
Showing 28 changed files with 3,060 additions and 1,327 deletions.
23 changes: 11 additions & 12 deletions docs/resources/elasticsearch_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ resource "elasticstack_elasticsearch_index" "my_index" {
}
})
number_of_shards = 1
number_of_replicas = 2
search_idle_after = "20s"
total_shards_per_node = 200
number_of_shards = 1
number_of_replicas = 2
search_idle_after = "20s"
}
```

Expand Down Expand Up @@ -76,7 +75,7 @@ resource "elasticstack_elasticsearch_index" "my_index" {
- `codec` (String) The `default` value compresses stored data with LZ4 compression, but this can be set to `best_compression` which uses DEFLATE for a higher compression ratio. This can be set only on creation.
- `default_pipeline` (String) The default ingest node pipeline for this index. Index requests will fail if the default pipeline is set and the pipeline does not exist.
- `deletion_protection` (Boolean) Whether to allow Terraform to destroy the index. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply command that deletes the instance will fail.
- `elasticsearch_connection` (Block List, Max: 1, Deprecated) Elasticsearch connection configuration block. This property will be removed in a future provider version. Configure the Elasticsearch connection via the provider configuration instead. (see [below for nested schema](#nestedblock--elasticsearch_connection))
- `elasticsearch_connection` (Block List, Deprecated) Elasticsearch connection configuration block. (see [below for nested schema](#nestedblock--elasticsearch_connection))
- `final_pipeline` (String) Final ingest pipeline for the index. Indexing requests will fail if the final pipeline is set and the pipeline does not exist. The final pipeline always runs after the request pipeline (if specified) and the default pipeline (if it exists). The special pipeline name _none indicates no ingest pipeline will run.
- `gc_deletes` (String) The length of time that a deleted document's version number remains available for further versioned operations.
- `highlight_max_analyzed_offset` (Number) The maximum number of characters that will be analyzed for a highlight request.
Expand All @@ -90,10 +89,10 @@ resource "elasticstack_elasticsearch_index" "my_index" {
- `load_fixed_bitset_filters_eagerly` (Boolean) Indicates whether cached filters are pre-loaded for nested queries. This can be set only on creation.
- `mapping_coerce` (Boolean) Set index level coercion setting that is applied to all mapping types.
- `mappings` (String) Mapping for fields in the index.
If specified, this mapping can include: field names, [field data types](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html), [mapping parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html).
**NOTE:**
- Changing datatypes in the existing _mappings_ will force index to be re-created.
- Removing field will be ignored by default same as elasticsearch. You need to recreate the index to remove field completely.
If specified, this mapping can include: field names, [field data types](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html), [mapping parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html).
**NOTE:**
- Changing datatypes in the existing _mappings_ will force index to be re-created.
- Removing field will be ignored by default same as elasticsearch. You need to recreate the index to remove field completely.
- `master_timeout` (String) Period to wait for a connection to the master node. If no response is received before the timeout expires, the request fails and returns an error. Defaults to `30s`. This value is ignored when running against Serverless projects.
- `max_docvalue_fields_search` (Number) The maximum number of `docvalue_fields` that are allowed in a query.
- `max_inner_result_window` (Number) The maximum value of `from + size` for inner hits definition and top hits aggregations to this index.
Expand Down Expand Up @@ -123,7 +122,7 @@ If specified, this mapping can include: field names, [field data types](https://
- `search_slowlog_threshold_query_info` (String) Set the cutoff for shard level slow search logging of slow searches in the query phase, in time units, e.g. `5s`
- `search_slowlog_threshold_query_trace` (String) Set the cutoff for shard level slow search logging of slow searches in the query phase, in time units, e.g. `500ms`
- `search_slowlog_threshold_query_warn` (String) Set the cutoff for shard level slow search logging of slow searches in the query phase, in time units, e.g. `10s`
- `settings` (Block List, Max: 1, Deprecated) DEPRECATED: Please use dedicated setting field. Configuration options for the index. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#index-modules-settings.
- `settings` (Block List, Deprecated) DEPRECATED: Please use dedicated setting field. Configuration options for the index. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#index-modules-settings.
**NOTE:** Static index settings (see: https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#_static_index_settings) can be only set on the index creation and later cannot be removed or updated - _apply_ will return error (see [below for nested schema](#nestedblock--settings))
- `shard_check_on_startup` (String) Whether or not shards should be checked for corruption before opening. When corruption is detected, it will prevent the shard from being opened. Accepts `false`, `true`, `checksum`.
- `sort_field` (Set of String) The field to sort shards in this index by.
Expand Down Expand Up @@ -177,9 +176,9 @@ Optional:
<a id="nestedblock--settings"></a>
### Nested Schema for `settings`

Required:
Optional:

- `setting` (Block Set, Min: 1) Defines the setting for the index. (see [below for nested schema](#nestedblock--settings--setting))
- `setting` (Block Set) Defines the setting for the index. (see [below for nested schema](#nestedblock--settings--setting))

<a id="nestedblock--settings--setting"></a>
### Nested Schema for `settings.setting`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ resource "elasticstack_elasticsearch_index" "my_index" {
}
})

number_of_shards = 1
number_of_replicas = 2
search_idle_after = "20s"
total_shards_per_node = 200
number_of_shards = 1
number_of_replicas = 2
search_idle_after = "20s"
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.23.1
require (
github.com/disaster37/go-kibana-rest/v8 v8.5.0
github.com/elastic/go-elasticsearch/v7 v7.17.10
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/go-version v1.7.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
Expand Down
30 changes: 30 additions & 0 deletions internal/clients/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/hashicorp/go-version"
fwdiags "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
Expand Down Expand Up @@ -156,6 +157,35 @@ func ConvertProviderData(providerData any) (*ApiClient, fwdiags.Diagnostics) {
return client, diags
}

func MaybeNewApiClientFromFrameworkResource(ctx context.Context, esConnList types.List, defaultClient *ApiClient) (*ApiClient, fwdiags.Diagnostics) {
var esConns []config.ElasticsearchConnection
if diags := esConnList.ElementsAs(ctx, &esConns, true); diags.HasError() {
return nil, diags
}

if len(esConns) == 0 {
return defaultClient, nil
}

cfg, diags := config.NewFromFramework(ctx, config.ProviderConfiguration{Elasticsearch: esConns}, defaultClient.version)
if diags.HasError() {
return nil, diags
}

esClient, err := buildEsClient(cfg)
if err != nil {
return nil, fwdiags.Diagnostics{fwdiags.NewErrorDiagnostic(err.Error(), err.Error())}
}

return &ApiClient{
elasticsearch: esClient,
elasticsearchClusterInfo: defaultClient.elasticsearchClusterInfo,
kibana: defaultClient.kibana,
fleet: defaultClient.fleet,
version: defaultClient.version,
}, diags
}

func NewApiClientFromSDKResource(d *schema.ResourceData, meta interface{}) (*ApiClient, diag.Diagnostics) {
defaultClient := meta.(*ApiClient)
version := defaultClient.version
Expand Down
137 changes: 76 additions & 61 deletions internal/clients/elasticsearch/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/models"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
fwdiags "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
)

Expand Down Expand Up @@ -245,16 +246,19 @@ func DeleteIndexTemplate(ctx context.Context, apiClient *clients.ApiClient, temp
return diags
}

func PutIndex(ctx context.Context, apiClient *clients.ApiClient, index *models.Index, params *models.PutIndexParams) diag.Diagnostics {
var diags diag.Diagnostics
func PutIndex(ctx context.Context, apiClient *clients.ApiClient, index *models.Index, params *models.PutIndexParams) fwdiags.Diagnostics {
indexBytes, err := json.Marshal(index)
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}

esClient, err := apiClient.GetESClient()
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}

opts := []func(*esapi.IndicesCreateRequest){
Expand All @@ -272,45 +276,46 @@ func PutIndex(ctx context.Context, apiClient *clients.ApiClient, index *models.I
opts...,
)
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
defer res.Body.Close()
if diags := utils.CheckError(res, fmt.Sprintf("Unable to create index: %s", index.Name)); diags.HasError() {
return diags
}
return diags
diags := utils.CheckError(res, fmt.Sprintf("Unable to create index: %s", index.Name))
return utils.FrameworkDiagsFromSDK(diags)
}

func DeleteIndex(ctx context.Context, apiClient *clients.ApiClient, name string) diag.Diagnostics {
var diags diag.Diagnostics

func DeleteIndex(ctx context.Context, apiClient *clients.ApiClient, name string) fwdiags.Diagnostics {
esClient, err := apiClient.GetESClient()
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
res, err := esClient.Indices.Delete([]string{name}, esClient.Indices.Delete.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
defer res.Body.Close()
if diags := utils.CheckError(res, fmt.Sprintf("Unable to delete the index: %s", name)); diags.HasError() {
return diags
}

return diags
diags := utils.CheckError(res, fmt.Sprintf("Unable to delete the index: %s", name))
return utils.FrameworkDiagsFromSDK(diags)
}

func GetIndex(ctx context.Context, apiClient *clients.ApiClient, name string) (*models.Index, diag.Diagnostics) {
var diags diag.Diagnostics

func GetIndex(ctx context.Context, apiClient *clients.ApiClient, name string) (*models.Index, fwdiags.Diagnostics) {
esClient, err := apiClient.GetESClient()
if err != nil {
return nil, diag.FromErr(err)
return nil, fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
req := esClient.Indices.Get.WithFlatSettings(true)
res, err := esClient.Indices.Get([]string{name}, req, esClient.Indices.Get.WithContext(ctx))
if err != nil {
return nil, diag.FromErr(err)
return nil, fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
defer res.Body.Close()
// if there is no index found, return the empty struct, which should force the creation of the index
Expand All @@ -319,94 +324,104 @@ func GetIndex(ctx context.Context, apiClient *clients.ApiClient, name string) (*
}

if diags := utils.CheckError(res, fmt.Sprintf("Unable to get requested index: %s", name)); diags.HasError() {
return nil, diags
return nil, utils.FrameworkDiagsFromSDK(diags)
}

indices := make(map[string]models.Index)
if err := json.NewDecoder(res.Body).Decode(&indices); err != nil {
return nil, diag.FromErr(err)
return nil, fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
index := indices[name]
return &index, diags
return &index, nil
}

func DeleteIndexAlias(ctx context.Context, apiClient *clients.ApiClient, index string, aliases []string) diag.Diagnostics {
var diags diag.Diagnostics
func DeleteIndexAlias(ctx context.Context, apiClient *clients.ApiClient, index string, aliases []string) fwdiags.Diagnostics {
esClient, err := apiClient.GetESClient()
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
res, err := esClient.Indices.DeleteAlias([]string{index}, aliases, esClient.Indices.DeleteAlias.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
defer res.Body.Close()
if diags := utils.CheckError(res, fmt.Sprintf("Unable to delete aliases '%v' for index '%s'", index, aliases)); diags.HasError() {
return diags
}
return diags
diags := utils.CheckError(res, fmt.Sprintf("Unable to delete aliases '%v' for index '%s'", index, aliases))
return utils.FrameworkDiagsFromSDK(diags)
}

func UpdateIndexAlias(ctx context.Context, apiClient *clients.ApiClient, index string, alias *models.IndexAlias) diag.Diagnostics {
var diags diag.Diagnostics
func UpdateIndexAlias(ctx context.Context, apiClient *clients.ApiClient, index string, alias *models.IndexAlias) fwdiags.Diagnostics {
aliasBytes, err := json.Marshal(alias)
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
esClient, err := apiClient.GetESClient()
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
req := esClient.Indices.PutAlias.WithBody(bytes.NewReader(aliasBytes))
res, err := esClient.Indices.PutAlias([]string{index}, alias.Name, req, esClient.Indices.PutAlias.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
defer res.Body.Close()
if diags := utils.CheckError(res, fmt.Sprintf("Unable to update alias '%v' for index '%s'", index, alias.Name)); diags.HasError() {
return diags
}
return diags
diags := utils.CheckError(res, fmt.Sprintf("Unable to update alias '%v' for index '%s'", index, alias.Name))
return utils.FrameworkDiagsFromSDK(diags)
}

func UpdateIndexSettings(ctx context.Context, apiClient *clients.ApiClient, index string, settings map[string]interface{}) diag.Diagnostics {
var diags diag.Diagnostics
func UpdateIndexSettings(ctx context.Context, apiClient *clients.ApiClient, index string, settings map[string]interface{}) fwdiags.Diagnostics {
settingsBytes, err := json.Marshal(settings)
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
esClient, err := apiClient.GetESClient()
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
req := esClient.Indices.PutSettings.WithIndex(index)
res, err := esClient.Indices.PutSettings(bytes.NewReader(settingsBytes), req, esClient.Indices.PutSettings.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
defer res.Body.Close()
if diags := utils.CheckError(res, "Unable to update index settings"); diags.HasError() {
return diags
}
return diags
diags := utils.CheckError(res, "Unable to update index settings")
return utils.FrameworkDiagsFromSDK(diags)
}

func UpdateIndexMappings(ctx context.Context, apiClient *clients.ApiClient, index, mappings string) diag.Diagnostics {
var diags diag.Diagnostics
func UpdateIndexMappings(ctx context.Context, apiClient *clients.ApiClient, index, mappings string) fwdiags.Diagnostics {
esClient, err := apiClient.GetESClient()
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
req := esClient.Indices.PutMapping.WithIndex(index)
res, err := esClient.Indices.PutMapping(strings.NewReader(mappings), req, esClient.Indices.PutMapping.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
return fwdiags.Diagnostics{
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
}
}
defer res.Body.Close()
if diags := utils.CheckError(res, "Unable to update index mappings"); diags.HasError() {
return diags
}
return diags
diags := utils.CheckError(res, "Unable to update index mappings")
return utils.FrameworkDiagsFromSDK(diags)
}

func PutDataStream(ctx context.Context, apiClient *clients.ApiClient, dataStreamName string) diag.Diagnostics {
Expand Down
Loading

0 comments on commit de65ccf

Please sign in to comment.