Skip to content

Commit

Permalink
Don't populate settings during read
Browse files Browse the repository at this point in the history
There's a fair bit of complexity around handling computed settings. I'll look at adding this back in a follow up PR, but want to get this resource in a working state so we can make a new release.

This covers the case where settings are applied as part of an index template
  • Loading branch information
tobio committed Sep 19, 2024
1 parent b38ec22 commit f0572ca
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 160 deletions.
7 changes: 5 additions & 2 deletions internal/clients/elasticsearch/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,11 @@ func GetIndex(ctx context.Context, apiClient *clients.ApiClient, name string) (*
return nil, diags
}

index := indices[name]
return &index, diags
if index, ok := indices[name]; ok {
return &index, nil
}

return nil, nil
}

func GetIndices(ctx context.Context, apiClient *clients.ApiClient, name string) (map[string]models.Index, fwdiags.Diagnostics) {
Expand Down
51 changes: 51 additions & 0 deletions internal/elasticsearch/index/index/acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,25 @@ func TestAccResourceIndexSettings(t *testing.T) {
})
}

func TestAccResourceIndexWithTemplate(t *testing.T) {
indexName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
CheckDestroy: checkResourceIndexDestroy,
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccResourceIndexWithTemplate(indexName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_elasticsearch_index.test", "name", indexName),
resource.TestCheckNoResourceAttr("elasticstack_elasticsearch_index.test", "default_pipeline"),
),
},
},
})
}

func TestAccResourceIndexRemovingField(t *testing.T) {
indexName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)

Expand Down Expand Up @@ -450,6 +469,38 @@ resource "elasticstack_elasticsearch_index" "test_settings_removing_field" {
`, name)
}

func testAccResourceIndexWithTemplate(name string) string {
return fmt.Sprintf(`
provider "elasticstack" {
elasticsearch {}
}
resource "elasticstack_elasticsearch_index_template" "test" {
name = "%s"
index_patterns = ["%s"]
template {
settings = jsonencode({
default_pipeline = ".fleet_final_pipeline-1"
lifecycle = { name = ".monitoring-8-ilm-policy" }
})
}
}
resource "elasticstack_elasticsearch_index" "test" {
name = "%s"
deletion_protection = false
alias {
name = "%s-alias"
is_write_index = true
}
lifecycle {
ignore_changes = [mappings]
}
depends_on = [elasticstack_elasticsearch_index_template.test]
}
`, name, name, name, name)
}

func checkResourceIndexDestroy(s *terraform.State) error {
client, err := clients.NewAcceptanceTestingClient()
if err != nil {
Expand Down
149 changes: 0 additions & 149 deletions internal/elasticsearch/index/index/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"

"github.com/elastic/terraform-provider-elasticstack/internal/clients"
Expand Down Expand Up @@ -223,141 +222,6 @@ func aliasesFromAPI(ctx context.Context, apiModel models.Index) (basetypes.SetVa
}

func setSettingsFromAPI(ctx context.Context, model *tfModel, apiModel models.Index) diag.Diagnostics {
modelType := reflect.TypeOf(*model)

for _, key := range dynamicSettingsKeys {
settingsValue, ok := apiModel.Settings["index."+key]
var tfValue attr.Value
if !ok {
continue
}

tfFieldKey := 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:
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{
Expand Down Expand Up @@ -569,19 +433,6 @@ func (model tfModel) toIndexSettings(ctx context.Context) (map[string]interface{
return settings, nil
}

func (model *tfModel) 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
}

func (model tfModel) getFieldValueByTagValue(tagName string, t reflect.Type) (attr.Value, bool) {
numField := t.NumField()
for i := 0; i < numField; i++ {
Expand Down
30 changes: 21 additions & 9 deletions internal/elasticsearch/index/index/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index"
providerschema "github.com/elastic/terraform-provider-elasticstack/internal/schema"
"github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
"github.com/elastic/terraform-provider-elasticstack/internal/utils/planmodifiers"
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
Expand Down Expand Up @@ -58,31 +59,46 @@ func getSchema() schema.Schema {
Description: "Value used to route indexing operations to a specific shard. If specified, this overwrites the `routing` value for indexing operations.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
planmodifiers.StringUseDefaultIfUnknown(""),
},
},
"is_hidden": schema.BoolAttribute{
Description: "If true, the alias is hidden.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
planmodifiers.BoolUseDefaultIfUnknown(false),
},
},
"is_write_index": schema.BoolAttribute{
Description: "If true, the index is the write index for the alias.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
planmodifiers.BoolUseDefaultIfUnknown(false),
},
},
"routing": schema.StringAttribute{
Description: "Value used to route indexing and search operations to a specific shard.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
planmodifiers.StringUseDefaultIfUnknown(""),
},
},
"search_routing": schema.StringAttribute{
Description: "Value used to route search operations to a specific shard. If specified, this overwrites the routing value for search operations.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
planmodifiers.StringUseDefaultIfUnknown(""),
},
},
},
},
Expand Down Expand Up @@ -216,10 +232,6 @@ func getSchema() schema.Schema {
"number_of_replicas": schema.Int64Attribute{
Description: "Number of shard replicas.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"auto_expand_replicas": schema.StringAttribute{
Description: "Set the number of replicas to the node count in the cluster. Set to a dash delimited lower and upper bound (e.g. 0-5) or use all for the upper bound (e.g. 0-all)",
Expand Down
39 changes: 39 additions & 0 deletions internal/utils/planmodifiers/bool_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package planmodifiers

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

func BoolUseDefaultIfUnknown(defaultValue bool) boolDefault {
return boolDefault{defaultValue: defaultValue}
}

type boolDefault struct {
defaultValue bool
}

func (bd boolDefault) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = basetypes.NewBoolValue(bd.defaultValue)
}

func (bd boolDefault) Description(context.Context) string {
return fmt.Sprintf("Sets the value to [%t] if unknown", bd.defaultValue)
}

func (bd boolDefault) MarkdownDescription(ctx context.Context) string {
return bd.Description(ctx)
}
39 changes: 39 additions & 0 deletions internal/utils/planmodifiers/string_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package planmodifiers

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

func StringUseDefaultIfUnknown(defaultValue string) stringDefault {
return stringDefault{defaultValue: defaultValue}
}

type stringDefault struct {
defaultValue string
}

func (bd stringDefault) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = basetypes.NewStringValue(bd.defaultValue)
}

func (bd stringDefault) Description(context.Context) string {
return fmt.Sprintf("Sets the value to [%s] if unknown", bd.defaultValue)
}

func (bd stringDefault) MarkdownDescription(ctx context.Context) string {
return bd.Description(ctx)
}

0 comments on commit f0572ca

Please sign in to comment.