Skip to content

Commit c125f94

Browse files
authored
Ignore empty fields in API response (#1506)
* Ignore empty fields in API response * Changelog * Stringish
1 parent 758e34a commit c125f94

File tree

10 files changed

+89
-102
lines changed

10 files changed

+89
-102
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ alias = [
4040

4141
### Changes
4242

43+
- Fix an issue where the `elasticstack_fleet_output` resource would error due to inconsistent state after an ouptut was edited in the Kibana UI ([#1506](https://github.com/elastic/terraform-provider-elasticstack/pull/1506))
4344
- Allow `index` and `data_view_id` values to both be unknown during planning in `elasticstack_kibana_security_detection_rule` ([#1499](https://github.com/elastic/terraform-provider-elasticstack/pull/1499))
4445
- Support `.bedrock` and `.gen-ai` connectors ([#1467](https://github.com/elastic/terraform-provider-elasticstack/pull/1467))
4546
- Support the `solution` attribute in `elasticstack_kibana_space` from 8.16 ([#1486](https://github.com/elastic/terraform-provider-elasticstack/pull/1486))

internal/elasticsearch/index/alias/models.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"reflect"
77

88
"github.com/elastic/terraform-provider-elasticstack/internal/models"
9+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/typeutils"
910
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
1011
"github.com/hashicorp/terraform-plugin-framework/diag"
1112
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -95,19 +96,13 @@ func (model *tfModel) populateFromAPI(ctx context.Context, aliasName string, ind
9596
// indexFromAlias converts a models.IndexAlias to an indexModel
9697
func indexFromAlias(indexName string, aliasData models.IndexAlias) (indexModel, diag.Diagnostics) {
9798
index := indexModel{
98-
Name: types.StringValue(indexName),
99-
IsHidden: types.BoolValue(aliasData.IsHidden),
99+
Name: types.StringValue(indexName),
100+
IsHidden: types.BoolValue(aliasData.IsHidden),
101+
IndexRouting: typeutils.NonEmptyStringishValue(aliasData.IndexRouting),
102+
Routing: typeutils.NonEmptyStringishValue(aliasData.Routing),
103+
SearchRouting: typeutils.NonEmptyStringishValue(aliasData.SearchRouting),
100104
}
101105

102-
if aliasData.IndexRouting != "" {
103-
index.IndexRouting = types.StringValue(aliasData.IndexRouting)
104-
}
105-
if aliasData.Routing != "" {
106-
index.Routing = types.StringValue(aliasData.Routing)
107-
}
108-
if aliasData.SearchRouting != "" {
109-
index.SearchRouting = types.StringValue(aliasData.SearchRouting)
110-
}
111106
if aliasData.Filter != nil {
112107
filterBytes, err := json.Marshal(aliasData.Filter)
113108
if err != nil {

internal/elasticsearch/ml/anomaly_detection_job/models_tf.go

Lines changed: 17 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
99
"github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
10+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/typeutils"
1011
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
1112
fwdiags "github.com/hashicorp/terraform-plugin-framework/diag"
1213
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -300,10 +301,7 @@ func (tfModel *AnomalyDetectionJobTFModel) fromAPIModel(ctx context.Context, api
300301

301302
// Convert optional fields
302303
tfModel.AllowLazyOpen = types.BoolPointerValue(apiModel.AllowLazyOpen)
303-
304-
if apiModel.BackgroundPersistInterval != "" {
305-
tfModel.BackgroundPersistInterval = types.StringValue(apiModel.BackgroundPersistInterval)
306-
}
304+
tfModel.BackgroundPersistInterval = typeutils.NonEmptyStringishValue(apiModel.BackgroundPersistInterval)
307305

308306
if apiModel.CustomSettings != nil {
309307
customSettingsJSON, err := json.Marshal(apiModel.CustomSettings)
@@ -324,10 +322,7 @@ func (tfModel *AnomalyDetectionJobTFModel) fromAPIModel(ctx context.Context, api
324322
tfModel.RenormalizationWindowDays = types.Int64Value(*apiModel.RenormalizationWindowDays)
325323
}
326324

327-
if apiModel.ResultsIndexName != "" {
328-
tfModel.ResultsIndexName = types.StringValue(apiModel.ResultsIndexName)
329-
}
330-
325+
tfModel.ResultsIndexName = typeutils.NonEmptyStringishValue(apiModel.ResultsIndexName)
331326
tfModel.ResultsRetentionDays = types.Int64PointerValue(apiModel.ResultsRetentionDays)
332327

333328
// Convert analysis_config
@@ -363,29 +358,10 @@ func (tfModel *AnomalyDetectionJobTFModel) convertAnalysisConfigFromAPI(ctx cont
363358
}
364359

365360
// Convert optional string fields
366-
if apiConfig.CategorizationFieldName != "" {
367-
analysisConfigTF.CategorizationFieldName = types.StringValue(apiConfig.CategorizationFieldName)
368-
} else {
369-
analysisConfigTF.CategorizationFieldName = types.StringNull()
370-
}
371-
372-
if apiConfig.Latency != "" {
373-
analysisConfigTF.Latency = types.StringValue(apiConfig.Latency)
374-
} else {
375-
analysisConfigTF.Latency = types.StringNull()
376-
}
377-
378-
if apiConfig.ModelPruneWindow != "" {
379-
analysisConfigTF.ModelPruneWindow = types.StringValue(apiConfig.ModelPruneWindow)
380-
} else {
381-
analysisConfigTF.ModelPruneWindow = types.StringNull()
382-
}
383-
384-
if apiConfig.SummaryCountFieldName != "" {
385-
analysisConfigTF.SummaryCountFieldName = types.StringValue(apiConfig.SummaryCountFieldName)
386-
} else {
387-
analysisConfigTF.SummaryCountFieldName = types.StringNull()
388-
}
361+
analysisConfigTF.CategorizationFieldName = typeutils.NonEmptyStringishValue(apiConfig.CategorizationFieldName)
362+
analysisConfigTF.Latency = typeutils.NonEmptyStringishValue(apiConfig.Latency)
363+
analysisConfigTF.ModelPruneWindow = typeutils.NonEmptyStringishValue(apiConfig.ModelPruneWindow)
364+
analysisConfigTF.SummaryCountFieldName = typeutils.NonEmptyStringishValue(apiConfig.SummaryCountFieldName)
389365

390366
// Convert boolean fields
391367
analysisConfigTF.MultivariateByFields = types.BoolPointerValue(apiConfig.MultivariateByFields)
@@ -417,41 +393,12 @@ func (tfModel *AnomalyDetectionJobTFModel) convertAnalysisConfigFromAPI(ctx cont
417393
}
418394

419395
// Convert optional string fields
420-
if detector.FieldName != "" {
421-
detectorsTF[i].FieldName = types.StringValue(detector.FieldName)
422-
} else {
423-
detectorsTF[i].FieldName = types.StringNull()
424-
}
425-
426-
if detector.ByFieldName != "" {
427-
detectorsTF[i].ByFieldName = types.StringValue(detector.ByFieldName)
428-
} else {
429-
detectorsTF[i].ByFieldName = types.StringNull()
430-
}
431-
432-
if detector.OverFieldName != "" {
433-
detectorsTF[i].OverFieldName = types.StringValue(detector.OverFieldName)
434-
} else {
435-
detectorsTF[i].OverFieldName = types.StringNull()
436-
}
437-
438-
if detector.PartitionFieldName != "" {
439-
detectorsTF[i].PartitionFieldName = types.StringValue(detector.PartitionFieldName)
440-
} else {
441-
detectorsTF[i].PartitionFieldName = types.StringNull()
442-
}
443-
444-
if detector.DetectorDescription != "" {
445-
detectorsTF[i].DetectorDescription = types.StringValue(detector.DetectorDescription)
446-
} else {
447-
detectorsTF[i].DetectorDescription = types.StringNull()
448-
}
449-
450-
if detector.ExcludeFrequent != "" {
451-
detectorsTF[i].ExcludeFrequent = types.StringValue(detector.ExcludeFrequent)
452-
} else {
453-
detectorsTF[i].ExcludeFrequent = types.StringNull()
454-
}
396+
detectorsTF[i].FieldName = typeutils.NonEmptyStringishValue(detector.FieldName)
397+
detectorsTF[i].ByFieldName = typeutils.NonEmptyStringishValue(detector.ByFieldName)
398+
detectorsTF[i].OverFieldName = typeutils.NonEmptyStringishValue(detector.OverFieldName)
399+
detectorsTF[i].PartitionFieldName = typeutils.NonEmptyStringishValue(detector.PartitionFieldName)
400+
detectorsTF[i].DetectorDescription = typeutils.NonEmptyStringishValue(detector.DetectorDescription)
401+
detectorsTF[i].ExcludeFrequent = typeutils.NonEmptyStringishValue(detector.ExcludeFrequent)
455402

456403
// Convert boolean field
457404
detectorsTF[i].UseNull = types.BoolPointerValue(detector.UseNull)
@@ -530,18 +477,9 @@ func (tfModel *AnomalyDetectionJobTFModel) convertDataDescriptionFromAPI(ctx con
530477
return types.ObjectNull(getDataDescriptionAttrTypes())
531478
}
532479

533-
dataDescriptionTF := DataDescriptionTFModel{}
534-
535-
if apiDataDescription.TimeField != "" {
536-
dataDescriptionTF.TimeField = types.StringValue(apiDataDescription.TimeField)
537-
} else {
538-
dataDescriptionTF.TimeField = types.StringNull()
539-
}
540-
541-
if apiDataDescription.TimeFormat != "" {
542-
dataDescriptionTF.TimeFormat = types.StringValue(apiDataDescription.TimeFormat)
543-
} else {
544-
dataDescriptionTF.TimeFormat = types.StringNull()
480+
dataDescriptionTF := DataDescriptionTFModel{
481+
TimeField: typeutils.NonEmptyStringishValue(apiDataDescription.TimeField),
482+
TimeFormat: typeutils.NonEmptyStringishValue(apiDataDescription.TimeFormat),
545483
}
546484

547485
dataDescriptionObjectValue, d := types.ObjectValueFrom(ctx, getDataDescriptionAttrTypes(), dataDescriptionTF)
@@ -576,12 +514,7 @@ func (tfModel *AnomalyDetectionJobTFModel) convertModelPlotConfigFromAPI(ctx con
576514

577515
modelPlotConfigTF := ModelPlotConfigTFModel{
578516
Enabled: types.BoolValue(apiModelPlotConfig.Enabled),
579-
}
580-
581-
if apiModelPlotConfig.Terms != "" {
582-
modelPlotConfigTF.Terms = types.StringValue(apiModelPlotConfig.Terms)
583-
} else {
584-
modelPlotConfigTF.Terms = types.StringNull()
517+
Terms: typeutils.NonEmptyStringishValue(apiModelPlotConfig.Terms),
585518
}
586519

587520
modelPlotConfigTF.AnnotationsEnabled = types.BoolPointerValue(apiModelPlotConfig.AnnotationsEnabled)

internal/fleet/output/models_elasticsearch.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
77
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/typeutils"
89
"github.com/hashicorp/terraform-plugin-framework/diag"
910
"github.com/hashicorp/terraform-plugin-framework/path"
1011
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -17,7 +18,7 @@ func (model *outputModel) fromAPIElasticsearchModel(ctx context.Context, data *k
1718
model.Type = types.StringValue(string(data.Type))
1819
model.Hosts = utils.SliceToListType_String(ctx, data.Hosts, path.Root("hosts"), &diags)
1920
model.CaSha256 = types.StringPointerValue(data.CaSha256)
20-
model.CaTrustedFingerprint = types.StringPointerValue(data.CaTrustedFingerprint)
21+
model.CaTrustedFingerprint = typeutils.NonEmptyStringishPointerValue(data.CaTrustedFingerprint)
2122
model.DefaultIntegrations = types.BoolPointerValue(data.IsDefault)
2223
model.DefaultMonitoring = types.BoolPointerValue(data.IsDefaultMonitoring)
2324
model.ConfigYaml = types.StringPointerValue(data.ConfigYaml)

internal/fleet/output/models_kafka.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
77
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/typeutils"
89
"github.com/hashicorp/terraform-plugin-framework/diag"
910
"github.com/hashicorp/terraform-plugin-framework/path"
1011
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -437,7 +438,7 @@ func (model *outputModel) fromAPIKafkaModel(ctx context.Context, data *kbapi.Out
437438
model.Type = types.StringValue(string(data.Type))
438439
model.Hosts = utils.SliceToListType_String(ctx, data.Hosts, path.Root("hosts"), &diags)
439440
model.CaSha256 = types.StringPointerValue(data.CaSha256)
440-
model.CaTrustedFingerprint = types.StringPointerValue(data.CaTrustedFingerprint)
441+
model.CaTrustedFingerprint = typeutils.NonEmptyStringishPointerValue(data.CaTrustedFingerprint)
441442
model.DefaultIntegrations = types.BoolPointerValue(data.IsDefault)
442443
model.DefaultMonitoring = types.BoolPointerValue(data.IsDefaultMonitoring)
443444
model.ConfigYaml = types.StringPointerValue(data.ConfigYaml)

internal/fleet/output/models_logstash.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
77
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/typeutils"
89
"github.com/hashicorp/terraform-plugin-framework/diag"
910
"github.com/hashicorp/terraform-plugin-framework/path"
1011
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -17,7 +18,7 @@ func (model *outputModel) fromAPILogstashModel(ctx context.Context, data *kbapi.
1718
model.Type = types.StringValue(string(data.Type))
1819
model.Hosts = utils.SliceToListType_String(ctx, data.Hosts, path.Root("hosts"), &diags)
1920
model.CaSha256 = types.StringPointerValue(data.CaSha256)
20-
model.CaTrustedFingerprint = types.StringPointerValue(data.CaTrustedFingerprint)
21+
model.CaTrustedFingerprint = typeutils.NonEmptyStringishPointerValue(data.CaTrustedFingerprint)
2122
model.DefaultIntegrations = types.BoolPointerValue(data.IsDefault)
2223
model.DefaultMonitoring = types.BoolPointerValue(data.IsDefaultMonitoring)
2324
model.ConfigYaml = types.StringPointerValue(data.ConfigYaml)

internal/fleet/output/models_ssl.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
77
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/typeutils"
89
"github.com/hashicorp/terraform-plugin-framework/diag"
910
"github.com/hashicorp/terraform-plugin-framework/path"
1011
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -57,12 +58,21 @@ func sslToObjectValue(ctx context.Context, ssl *kbapi.OutputSsl) (types.Object,
5758
}
5859

5960
var diags diag.Diagnostics
60-
p := path.Root("ssl")
6161
sslModel := outputSslModel{
62-
CertificateAuthorities: utils.SliceToListType_String(ctx, utils.Deref(ssl.CertificateAuthorities), p.AtName("certificate_authorities"), &diags),
63-
Certificate: types.StringPointerValue(ssl.Certificate),
64-
Key: types.StringPointerValue(ssl.Key),
62+
Certificate: typeutils.NonEmptyStringishPointerValue(ssl.Certificate),
63+
Key: typeutils.NonEmptyStringishPointerValue(ssl.Key),
6564
}
65+
66+
if cas := utils.Deref(ssl.CertificateAuthorities); len(cas) > 0 {
67+
sslModel.CertificateAuthorities = utils.SliceToListType_String(ctx, cas, path.Root("ssl").AtName("certificate_authorities"), &diags)
68+
} else {
69+
sslModel.CertificateAuthorities = types.ListNull(types.StringType)
70+
}
71+
72+
if sslModel.CertificateAuthorities.IsNull() && sslModel.Certificate.IsNull() && sslModel.Key.IsNull() {
73+
return types.ObjectNull(getSslAttrTypes()), nil
74+
}
75+
6676
obj, diagTemp := types.ObjectValueFrom(ctx, getSslAttrTypes(), sslModel)
6777
diags.Append(diagTemp...)
6878
return obj, diags

internal/fleet/output/models_ssl_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,28 @@ func Test_sslToObjectValue(t *testing.T) {
122122
},
123123
want: types.ObjectNull(getSslAttrTypes()),
124124
},
125+
{
126+
name: "returns null object when ssl has all empty fields",
127+
args: args{
128+
ssl: &kbapi.OutputSsl{
129+
Certificate: nil,
130+
CertificateAuthorities: nil,
131+
Key: nil,
132+
},
133+
},
134+
want: types.ObjectNull(getSslAttrTypes()),
135+
},
136+
{
137+
name: "returns null object when ssl has empty string pointers and empty slice",
138+
args: args{
139+
ssl: &kbapi.OutputSsl{
140+
Certificate: utils.Pointer(""),
141+
CertificateAuthorities: &[]string{},
142+
Key: utils.Pointer(""),
143+
},
144+
},
145+
want: types.ObjectNull(getSslAttrTypes()),
146+
},
125147
{
126148
name: "returns an object when populated",
127149
args: args{

internal/fleet/output/schema.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,24 @@ func getSchema() schema.Schema {
108108
Description: "Server SSL certificate authorities.",
109109
Optional: true,
110110
ElementType: types.StringType,
111+
Validators: []validator.List{
112+
listvalidator.SizeAtLeast(1),
113+
},
111114
},
112115
"certificate": schema.StringAttribute{
113116
Description: "Client SSL certificate.",
114117
Required: true,
118+
Validators: []validator.String{
119+
stringvalidator.LengthAtLeast(1),
120+
},
115121
},
116122
"key": schema.StringAttribute{
117123
Description: "Client SSL certificate key.",
118124
Required: true,
119125
Sensitive: true,
126+
Validators: []validator.String{
127+
stringvalidator.LengthAtLeast(1),
128+
},
120129
},
121130
},
122131
},
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,17 @@ func StringishPointerValue[T ~string](ptr *T) types.String {
1414
func StringishValue[T ~string](value T) types.String {
1515
return types.StringValue(string(value))
1616
}
17+
18+
func NonEmptyStringishValue[T ~string](value T) types.String {
19+
if value == "" {
20+
return types.StringNull()
21+
}
22+
return types.StringValue(string(value))
23+
}
24+
25+
func NonEmptyStringishPointerValue[T ~string](ptr *T) types.String {
26+
if ptr == nil {
27+
return types.StringNull()
28+
}
29+
return NonEmptyStringishValue(*ptr)
30+
}

0 commit comments

Comments
 (0)