internal/elasticsearch/index/indices/models.go (399 lines of code) (raw):
package indices
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strconv"
"github.com/elastic/terraform-provider-elasticstack/internal/models"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
var (
staticSettingsKeys = []string{
"number_of_shards",
"number_of_routing_shards",
"codec",
"routing_partition_size",
"load_fixed_bitset_filters_eagerly",
"shard.check_on_startup",
"sort.field",
"sort.order",
"mapping.coerce",
}
dynamicSettingsKeys = []string{
"number_of_replicas",
"auto_expand_replicas",
"refresh_interval",
"search.idle.after",
"max_result_window",
"max_inner_result_window",
"max_rescore_window",
"max_docvalue_fields_search",
"max_script_fields",
"max_ngram_diff",
"max_shingle_diff",
"blocks.read_only",
"blocks.read_only_allow_delete",
"blocks.read",
"blocks.write",
"blocks.metadata",
"max_refresh_listeners",
"analyze.max_token_count",
"highlight.max_analyzed_offset",
"max_terms_count",
"max_regex_length",
"query.default_field",
"routing.allocation.enable",
"routing.rebalance.enable",
"gc_deletes",
"default_pipeline",
"final_pipeline",
"unassigned.node_left.delayed_timeout",
"search.slowlog.threshold.query.warn",
"search.slowlog.threshold.query.info",
"search.slowlog.threshold.query.debug",
"search.slowlog.threshold.query.trace",
"search.slowlog.threshold.fetch.warn",
"search.slowlog.threshold.fetch.info",
"search.slowlog.threshold.fetch.debug",
"search.slowlog.threshold.fetch.trace",
"search.slowlog.level",
"indexing.slowlog.threshold.index.warn",
"indexing.slowlog.threshold.index.info",
"indexing.slowlog.threshold.index.debug",
"indexing.slowlog.threshold.index.trace",
"indexing.slowlog.level",
"indexing.slowlog.source",
}
allSettingsKeys = []string{}
)
func init() {
allSettingsKeys = append(allSettingsKeys, staticSettingsKeys...)
allSettingsKeys = append(allSettingsKeys, dynamicSettingsKeys...)
}
type tfModel struct {
ID types.String `tfsdk:"id"`
Target types.String `tfsdk:"target"`
Indices types.List `tfsdk:"indices"`
}
type indexTfModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
NumberOfShards types.Int64 `tfsdk:"number_of_shards"`
NumberOfRoutingShards types.Int64 `tfsdk:"number_of_routing_shards"`
Codec types.String `tfsdk:"codec"`
RoutingPartitionSize types.Int64 `tfsdk:"routing_partition_size"`
LoadFixedBitsetFiltersEagerly types.Bool `tfsdk:"load_fixed_bitset_filters_eagerly"`
ShardCheckOnStartup types.String `tfsdk:"shard_check_on_startup"`
SortField types.Set `tfsdk:"sort_field"`
SortOrder types.List `tfsdk:"sort_order"`
MappingCoerce types.Bool `tfsdk:"mapping_coerce"`
NumberOfReplicas types.Int64 `tfsdk:"number_of_replicas"`
AutoExpandReplicas types.String `tfsdk:"auto_expand_replicas"`
SearchIdleAfter types.String `tfsdk:"search_idle_after"`
RefreshInterval types.String `tfsdk:"refresh_interval"`
MaxResultWindow types.Int64 `tfsdk:"max_result_window"`
MaxInnerResultWindow types.Int64 `tfsdk:"max_inner_result_window"`
MaxRescoreWindow types.Int64 `tfsdk:"max_rescore_window"`
MaxDocvalueFieldsSearch types.Int64 `tfsdk:"max_docvalue_fields_search"`
MaxScriptFields types.Int64 `tfsdk:"max_script_fields"`
MaxNGramDiff types.Int64 `tfsdk:"max_ngram_diff"`
MaxShingleDiff types.Int64 `tfsdk:"max_shingle_diff"`
MaxRefreshListeners types.Int64 `tfsdk:"max_refresh_listeners"`
AnalyzeMaxTokenCount types.Int64 `tfsdk:"analyze_max_token_count"`
HighlightMaxAnalyzedOffset types.Int64 `tfsdk:"highlight_max_analyzed_offset"`
MaxTermsCount types.Int64 `tfsdk:"max_terms_count"`
MaxRegexLength types.Int64 `tfsdk:"max_regex_length"`
QueryDefaultField types.Set `tfsdk:"query_default_field"`
RoutingAllocationEnable types.String `tfsdk:"routing_allocation_enable"`
RoutingRebalanceEnable types.String `tfsdk:"routing_rebalance_enable"`
GCDeletes types.String `tfsdk:"gc_deletes"`
BlocksReadOnly types.Bool `tfsdk:"blocks_read_only"`
BlocksReadOnlyAllowDelete types.Bool `tfsdk:"blocks_read_only_allow_delete"`
BlocksRead types.Bool `tfsdk:"blocks_read"`
BlocksWrite types.Bool `tfsdk:"blocks_write"`
BlocksMetadata types.Bool `tfsdk:"blocks_metadata"`
DefaultPipeline types.String `tfsdk:"default_pipeline"`
FinalPipeline types.String `tfsdk:"final_pipeline"`
UnassignedNodeLeftDelayedTimeout types.String `tfsdk:"unassigned_node_left_delayed_timeout"`
SearchSlowlogThresholdQueryWarn types.String `tfsdk:"search_slowlog_threshold_query_warn"`
SearchSlowlogThresholdQueryInfo types.String `tfsdk:"search_slowlog_threshold_query_info"`
SearchSlowlogThresholdQueryDebug types.String `tfsdk:"search_slowlog_threshold_query_debug"`
SearchSlowlogThresholdQueryTrace types.String `tfsdk:"search_slowlog_threshold_query_trace"`
SearchSlowlogThresholdFetchWarn types.String `tfsdk:"search_slowlog_threshold_fetch_warn"`
SearchSlowlogThresholdFetchInfo types.String `tfsdk:"search_slowlog_threshold_fetch_info"`
SearchSlowlogThresholdFetchDebug types.String `tfsdk:"search_slowlog_threshold_fetch_debug"`
SearchSlowlogThresholdFetchTrace types.String `tfsdk:"search_slowlog_threshold_fetch_trace"`
SearchSlowlogLevel types.String `tfsdk:"search_slowlog_level"`
IndexingSlowlogThresholdIndexWarn types.String `tfsdk:"indexing_slowlog_threshold_index_warn"`
IndexingSlowlogThresholdIndexInfo types.String `tfsdk:"indexing_slowlog_threshold_index_info"`
IndexingSlowlogThresholdIndexDebug types.String `tfsdk:"indexing_slowlog_threshold_index_debug"`
IndexingSlowlogThresholdIndexTrace types.String `tfsdk:"indexing_slowlog_threshold_index_trace"`
IndexingSlowlogLevel types.String `tfsdk:"indexing_slowlog_level"`
IndexingSlowlogSource types.String `tfsdk:"indexing_slowlog_source"`
AnalysisAnalyzer jsontypes.Normalized `tfsdk:"analysis_analyzer"`
AnalysisTokenizer jsontypes.Normalized `tfsdk:"analysis_tokenizer"`
AnalysisCharFilter jsontypes.Normalized `tfsdk:"analysis_char_filter"`
AnalysisFilter jsontypes.Normalized `tfsdk:"analysis_filter"`
AnalysisNormalizer jsontypes.Normalized `tfsdk:"analysis_normalizer"`
DeletionProtection types.Bool `tfsdk:"deletion_protection"`
WaitForActiveShards types.String `tfsdk:"wait_for_active_shards"`
MasterTimeout customtypes.Duration `tfsdk:"master_timeout"`
Timeout customtypes.Duration `tfsdk:"timeout"`
Mappings jsontypes.Normalized `tfsdk:"mappings"`
SettingsRaw jsontypes.Normalized `tfsdk:"settings_raw"`
Alias types.Set `tfsdk:"alias"`
}
type aliasTfModel struct {
Name types.String `tfsdk:"name"`
Filter jsontypes.Normalized `tfsdk:"filter"`
IndexRouting types.String `tfsdk:"index_routing"`
IsHidden types.Bool `tfsdk:"is_hidden"`
IsWriteIndex types.Bool `tfsdk:"is_write_index"`
Routing types.String `tfsdk:"routing"`
SearchRouting types.String `tfsdk:"search_routing"`
}
func (model *indexTfModel) populateFromAPI(ctx context.Context, indexName string, apiModel models.Index) diag.Diagnostics {
model.Name = types.StringValue(indexName)
model.SortField = types.SetValueMust(types.StringType, []attr.Value{})
model.SortOrder = types.ListValueMust(types.StringType, []attr.Value{})
model.QueryDefaultField = types.SetValueMust(types.StringType, []attr.Value{})
modelMappings, diags := mappingsFromAPI(apiModel)
if diags.HasError() {
return diags
}
modelAliases, diags := aliasesFromAPI(ctx, apiModel)
if diags.HasError() {
return diags
}
model.Mappings = modelMappings
model.Alias = modelAliases
diags = setSettingsFromAPI(ctx, model, apiModel)
if diags.HasError() {
return diags
}
return nil
}
func mappingsFromAPI(apiModel models.Index) (jsontypes.Normalized, diag.Diagnostics) {
if apiModel.Mappings != nil {
mappingBytes, err := json.Marshal(apiModel.Mappings)
if err != nil {
return jsontypes.NewNormalizedNull(), diag.Diagnostics{
diag.NewErrorDiagnostic("failed to marshal index mappings", err.Error()),
}
}
return jsontypes.NewNormalizedValue(string(mappingBytes)), nil
}
return jsontypes.NewNormalizedNull(), nil
}
func aliasesFromAPI(ctx context.Context, apiModel models.Index) (basetypes.SetValue, diag.Diagnostics) {
aliases := []aliasTfModel{}
for name, alias := range apiModel.Aliases {
tfAlias, diags := newAliasModelFromAPI(name, alias)
if diags.HasError() {
return basetypes.SetValue{}, diags
}
aliases = append(aliases, tfAlias)
}
modelAliases, diags := types.SetValueFrom(ctx, aliasElementType(), aliases)
if diags.HasError() {
return basetypes.SetValue{}, diags
}
return modelAliases, nil
}
func newAliasModelFromAPI(name string, apiModel models.IndexAlias) (aliasTfModel, diag.Diagnostics) {
tfAlias := aliasTfModel{
Name: types.StringValue(name),
IndexRouting: types.StringValue(apiModel.IndexRouting),
IsHidden: types.BoolValue(apiModel.IsHidden),
IsWriteIndex: types.BoolValue(apiModel.IsWriteIndex),
Routing: types.StringValue(apiModel.Routing),
SearchRouting: types.StringValue(apiModel.SearchRouting),
}
if apiModel.Filter != nil {
filterBytes, err := json.Marshal(apiModel.Filter)
if err != nil {
return aliasTfModel{}, diag.Diagnostics{
diag.NewErrorDiagnostic("failed to marshal alias filter", err.Error()),
}
}
tfAlias.Filter = jsontypes.NewNormalizedValue(string(filterBytes))
}
return tfAlias, nil
}
func setSettingsFromAPI(ctx context.Context, model *indexTfModel, apiModel models.Index) diag.Diagnostics {
modelType := reflect.TypeOf(*model)
for _, key := range allSettingsKeys {
settingsValue, ok := apiModel.Settings["index."+key]
var tfValue attr.Value
if !ok {
continue
}
tfFieldKey := utils.ConvertSettingsKeyToTFFieldKey(key)
value, ok := model.getFieldValueByTagValue(tfFieldKey, modelType)
if !ok {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to find setting value",
fmt.Sprintf("expected setting with key %s", tfFieldKey),
),
}
}
switch a := value.(type) {
case types.String:
settingStr, ok := settingsValue.(string)
if !ok {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to convert setting to string",
fmt.Sprintf("expected setting to be a string but got %t", settingsValue),
)}
}
tfValue = basetypes.NewStringValue(settingStr)
case types.Bool:
if settingStr, ok := settingsValue.(string); ok {
settingBool, err := strconv.ParseBool(settingStr)
if err != nil {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to convert setting to bool",
fmt.Sprintf("expected setting to be a bool but it was a string. Attempted to parse it but got %s", err.Error()),
),
}
}
settingsValue = settingBool
}
settingBool, ok := settingsValue.(bool)
if !ok {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to convert setting to bool",
fmt.Sprintf("expected setting to be a bool but got %t", settingsValue),
)}
}
tfValue = basetypes.NewBoolValue(settingBool)
case types.Int64:
if settingStr, ok := settingsValue.(string); ok {
settingInt, err := strconv.Atoi(settingStr)
if err != nil {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to convert setting to int",
fmt.Sprintf("expected setting to be an int but it was a string. Attempted to parse it but got %s", err.Error()),
),
}
}
settingsValue = int64(settingInt)
}
settingInt, ok := settingsValue.(int64)
if !ok {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to convert setting to int",
fmt.Sprintf("expected setting to be a int but got %t", settingsValue),
)}
}
tfValue = basetypes.NewInt64Value(settingInt)
case types.List:
elemType := a.ElementType(ctx)
if elemType != types.StringType {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"expected list of string",
fmt.Sprintf("expected list element type to be string but got %s", elemType),
),
}
}
elems, ok := settingsValue.([]interface{})
if !ok {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to convert setting to []string",
fmt.Sprintf("expected setting to be a []string but got %#v", settingsValue),
)}
}
var diags diag.Diagnostics
tfValue, diags = basetypes.NewListValueFrom(ctx, basetypes.StringType{}, elems)
if diags.HasError() {
return diags
}
case types.Set:
elemType := a.ElementType(ctx)
if elemType != types.StringType {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"expected set of string",
fmt.Sprintf("expected set element type to be string but got %s", elemType),
),
}
}
elems, ok := settingsValue.([]interface{})
if !ok {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to convert setting to []string",
fmt.Sprintf("expected setting to be a thing []string but got %#v", settingsValue),
)}
}
var diags diag.Diagnostics
tfValue, diags = basetypes.NewSetValueFrom(ctx, basetypes.StringType{}, elems)
if diags.HasError() {
return diags
}
default:
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"unknown value type",
fmt.Sprintf("unknown index setting value type %s", a.Type(ctx)),
),
}
}
ok = model.setFieldValueByTagValue(tfFieldKey, modelType, tfValue)
if !ok {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to find setting value",
fmt.Sprintf("expected setting with key %s", tfFieldKey),
),
}
}
}
settingsBytes, err := json.Marshal(apiModel.Settings)
if err != nil {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"failed to marshal raw settings",
err.Error(),
),
}
}
model.SettingsRaw = jsontypes.NewNormalizedValue(string(settingsBytes))
return nil
}
func (model indexTfModel) getFieldValueByTagValue(tagName string, t reflect.Type) (attr.Value, bool) {
numField := t.NumField()
for i := 0; i < numField; i++ {
field := t.Field(i)
if field.Tag.Get("tfsdk") == tagName {
return reflect.ValueOf(model).Field(i).Interface().(attr.Value), true
}
}
return nil, false
}
func (model *indexTfModel) setFieldValueByTagValue(tagName string, t reflect.Type, value attr.Value) bool {
numField := t.NumField()
for i := 0; i < numField; i++ {
field := t.Field(i)
if field.Tag.Get("tfsdk") == tagName {
reflect.ValueOf(model).Elem().Field(i).Set(reflect.ValueOf(value))
return true
}
}
return false
}