internal/elasticsearch/cluster/settings.go (258 lines of code) (raw):
package cluster
import (
"context"
"fmt"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func ResourceSettings() *schema.Resource {
settingSchema := &schema.Resource{
Schema: map[string]*schema.Schema{
"setting": {
Description: "Defines the setting in the cluster.",
Type: schema.TypeSet,
Required: true,
MinItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the setting to set and track.",
Type: schema.TypeString,
Required: true,
},
"value": {
Description: "The value of the setting to set and track.",
Type: schema.TypeString,
Optional: true,
},
"value_list": {
Description: "The list of values to be set for the key, where the list is required.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
},
},
},
}
settingsSchema := map[string]*schema.Schema{
"id": {
Description: "Internal identifier of the resource",
Type: schema.TypeString,
Computed: true,
},
"persistent": {
Description: "Settings will apply across restarts.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: settingSchema,
},
"transient": {
Description: "Settings do not survive a full cluster restart.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: settingSchema,
},
}
utils.AddConnectionSchema(settingsSchema)
return &schema.Resource{
Description: "Updates cluster-wide settings. If the Elasticsearch security features are enabled, you must have the manage cluster privilege to use this API. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-update-settings.html",
CreateContext: resourceClusterSettingsPut,
UpdateContext: resourceClusterSettingsPut,
ReadContext: resourceClusterSettingsRead,
DeleteContext: resourceClusterSettingsDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: settingsSchema,
}
}
func resourceClusterSettingsPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}
id, diags := client.ID(ctx, "cluster-settings")
if diags.HasError() {
return diags
}
settings, diags := getConfiguredSettings(d)
if diags.HasError() {
return diags
}
for _, v := range []string{"persistent", "transient"} {
if d.HasChange(v) {
old, new := d.GetChange(v)
diags = updateRemovedSettings(v, old, new, settings)
if diags.HasError() {
return diags
}
}
}
if diags := elasticsearch.PutSettings(ctx, client, settings); diags.HasError() {
return diags
}
d.SetId(id.String())
return resourceClusterSettingsRead(ctx, d, meta)
}
// Updates the map of settings in place if there is a difference between old and new list of settings
func updateRemovedSettings(name string, old, new interface{}, settings map[string]interface{}) diag.Diagnostics {
var diags diag.Diagnostics
if old != nil && new != nil {
oldSettings := make(map[string]interface{})
if len(old.([]interface{})) > 0 {
oldSettings, _ = expandSettings(old)
}
newSettings := make(map[string]interface{})
if len(new.([]interface{})) > 0 {
newSettings, diags = expandSettings(new)
if diags.HasError() {
return diags
}
}
if !utils.MapsEqual(oldSettings, newSettings) {
for s := range oldSettings {
if _, ok := newSettings[s]; !ok {
if settings[name] == nil {
settings[name] = make(map[string]interface{})
}
// make sure to remove the setting from the ES as well
settings[name].(map[string]interface{})[s] = nil
}
}
}
}
return diags
}
func getConfiguredSettings(d *schema.ResourceData) (map[string]interface{}, diag.Diagnostics) {
var diags diag.Diagnostics
settings := make(map[string]interface{})
if v, ok := d.GetOk("persistent"); ok {
var ds diag.Diagnostics
settings["persistent"], ds = expandSettings(v)
diags = append(diags, ds...)
}
if v, ok := d.GetOk("transient"); ok {
var ds diag.Diagnostics
settings["transient"], ds = expandSettings(v)
diags = append(diags, ds...)
}
return settings, diags
}
func expandSettings(s interface{}) (map[string]interface{}, diag.Diagnostics) {
var diags diag.Diagnostics
settings := s.([]interface{})[0].(map[string]interface{})["setting"].(*schema.Set)
result := make(map[string]interface{}, settings.Len())
for _, v := range settings.List() {
setting := v.(map[string]interface{})
settingName := setting["name"].(string)
if _, ok := result[settingName]; ok {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: fmt.Sprintf(`Unable to set "%s".`, settingName),
Detail: fmt.Sprintf(`Found setting "%s" have been already configured.`, settingName),
})
}
// check if the setting has value or value_list and act accordingly
if setting["value"].(string) != "" && len(setting["value_list"].([]interface{})) > 0 {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: `Only one of "value" or "value_list" can be set.`,
Detail: `Only one of "value" or "value_list" can be set.`,
})
return nil, diags
} else if setting["value"].(string) == "" && len(setting["value_list"].([]interface{})) == 0 {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: `At least one of "value" or "value_list" must be set to not empty value.`,
Detail: `At least one of "value" or "value_list" must be set to not empty value.`,
})
return nil, diags
}
if vv := setting["value"].(string); vv != "" {
result[settingName] = vv
} else {
result[settingName] = setting["value_list"]
}
}
return result, diags
}
func resourceClusterSettingsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}
clusterSettings, diags := elasticsearch.GetSettings(ctx, client)
if diags.HasError() {
return diags
}
configuredSettings, _ := getConfiguredSettings(d)
persistent := flattenSettings("persistent", configuredSettings, clusterSettings)
transient := flattenSettings("transient", configuredSettings, clusterSettings)
if err := d.Set("persistent", persistent); err != nil {
return diag.FromErr(err)
}
if err := d.Set("transient", transient); err != nil {
return diag.FromErr(err)
}
return diags
}
func flattenSettings(name string, old, new map[string]interface{}) []interface{} {
setting := make(map[string]interface{})
settings := make([]interface{}, 0)
result := make([]interface{}, 0)
if old[name] != nil {
for k := range old[name].(map[string]interface{}) {
if new[name] != nil {
if v, ok := new[name].(map[string]interface{})[k]; ok {
s := make(map[string]interface{})
s["name"] = k
// decide which value to set
switch t := v.(type) {
case string:
s["value"] = t
case []interface{}:
s["value_list"] = t
}
settings = append(settings, s)
}
}
}
}
if len(settings) > 0 {
setting["setting"] = settings
result = append(result, setting)
}
return result
}
func resourceClusterSettingsDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}
configuredSettings, _ := getConfiguredSettings(d)
pSettings := make(map[string]interface{})
if v := configuredSettings["persistent"]; v != nil {
for k := range v.(map[string]interface{}) {
pSettings[k] = nil
}
}
tSettings := make(map[string]interface{})
if v := configuredSettings["transient"]; v != nil {
for k := range v.(map[string]interface{}) {
tSettings[k] = nil
}
}
settings := map[string]interface{}{
"persistent": pSettings,
"transient": tSettings,
}
if diags := elasticsearch.PutSettings(ctx, client, settings); diags.HasError() {
return diags
}
return diags
}