diff --git a/CHANGELOG.md b/CHANGELOG.md index a9fc1e9d0..a7e5c7959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Fix a provider crash when interacting with elasticstack_kibana_data_view resources created with 0.11.0. ([#979](https://github.com/elastic/terraform-provider-elasticstack/pull/979)) - Add `max_primary_shard_docs` condition to ILM rollover ([#845](https://github.com/elastic/terraform-provider-elasticstack/pull/845)) - Add missing entries to `data_view.field_formats.params` ([#1001](https://github.com/elastic/terraform-provider-elasticstack/pull/1001)) +- Fix namespaces inconsistency when creating elasticstack_kibana_data_view resources ([#1011](https://github.com/elastic/terraform-provider-elasticstack/pull/1011)) ## [0.11.13] - 2025-01-09 @@ -19,6 +20,7 @@ - Support multiple group by fields in SLOs ([#870](https://github.com/elastic/terraform-provider-elasticstack/pull/878)). This changes to type of the `group_by` attribute of the `elasticstack_kibana_slo` resource from a String to a list of Strings. Any existing SLO defintions will need to update `group_by = "field"` to `group_by = ["field"]`. ### Changes + - Handle NPE in integration policy secrets ([#946](https://github.com/elastic/terraform-provider-elasticstack/pull/946)) - Use the auto-generated OAS schema from elastic/kibana for the Fleet API. ([#834](https://github.com/elastic/terraform-provider-elasticstack/issues/834)) - Support description in `elasticstack_elasticsearch_security_role` data sources. ([#884](https://github.com/elastic/terraform-provider-elasticstack/pull/884)) diff --git a/internal/kibana/data_view/create.go b/internal/kibana/data_view/create.go index 0473024cb..8756a6992 100644 --- a/internal/kibana/data_view/create.go +++ b/internal/kibana/data_view/create.go @@ -35,7 +35,7 @@ func (r *DataViewResource) Create(ctx context.Context, req resource.CreateReques return } - diags = planModel.populateFromAPI(ctx, dataView) + diags = planModel.populateFromAPI(ctx, dataView, spaceID) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/internal/kibana/data_view/models.go b/internal/kibana/data_view/models.go index e73dc4d80..7b556e682 100644 --- a/internal/kibana/data_view/models.go +++ b/internal/kibana/data_view/models.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -func (model *dataViewModel) populateFromAPI(ctx context.Context, data *kbapi.DataViewsDataViewResponseObject) diag.Diagnostics { +func (model *dataViewModel) populateFromAPI(ctx context.Context, data *kbapi.DataViewsDataViewResponseObject, spaceID string) diag.Diagnostics { if data == nil { return nil } @@ -60,10 +60,15 @@ func (model *dataViewModel) populateFromAPI(ctx context.Context, data *kbapi.Dat // Keep the original ordering if equal but unordered // The API response is ordered by name. - if len(existing) == len(incoming) { + // Additionally, allow for the response containing an extra namespace that is the current SpaceID + // If the SpaceID is not included in the `namespaces` field, when trying to GET the object it will 404 + if (len(existing) == len(incoming)) || (len(existing) == len(incoming)-1) { useExisting := true for _, ns := range existing { if !slices.Contains(incoming, ns) { + if ns == spaceID { + continue + } useExisting = false break } diff --git a/internal/kibana/data_view/models_test.go b/internal/kibana/data_view/models_test.go index bc65e7c30..4cec22bd9 100644 --- a/internal/kibana/data_view/models_test.go +++ b/internal/kibana/data_view/models_test.go @@ -100,13 +100,115 @@ func TestPopulateFromAPI(t *testing.T) { }, getDataViewAttrTypes(), path.Root("data_view"), &diags), }, }, + { + // When sending no value, the response from Kibana is ["default"] + name: "handleNamespaces_null_default", + existingModel: dataViewModel{ + ID: types.StringValue("default/id"), + SpaceID: types.StringValue("default"), + DataView: utils.ObjectValueFrom(ctx, &innerModel{ + ID: types.StringValue("id"), + Namespaces: utils.ListValueFrom[string](ctx, nil, types.StringType, path.Root("data_view").AtName("namespaces"), &diags), + SourceFilters: types.ListNull(types.StringType), + FieldAttributes: types.MapNull(getFieldAttrElemType()), + RuntimeFieldMap: types.MapNull(getRuntimeFieldMapElemType()), + FieldFormats: types.MapNull(getFieldFormatElemType()), + }, getDataViewAttrTypes(), path.Root("data_view"), &diags), + }, + response: kbapi.DataViewsDataViewResponseObject{ + DataView: &kbapi.DataViewsDataViewResponseObjectInner{ + Id: utils.Pointer("id"), + Namespaces: &[]string{"default"}, + }, + }, + expectedModel: dataViewModel{ + ID: types.StringValue("default/id"), + SpaceID: types.StringValue("default"), + DataView: utils.ObjectValueFrom(ctx, &innerModel{ + ID: types.StringValue("id"), + Namespaces: utils.ListValueFrom[string](ctx, nil, types.StringType, path.Root("data_view").AtName("namespaces"), &diags), + SourceFilters: types.ListNull(types.StringType), + FieldAttributes: types.MapNull(getFieldAttrElemType()), + RuntimeFieldMap: types.MapNull(getRuntimeFieldMapElemType()), + FieldFormats: types.MapNull(getFieldFormatElemType()), + }, getDataViewAttrTypes(), path.Root("data_view"), &diags), + }, + }, + { + // When sending the SpaceID as the namespace, the response from Kibana should be the same + name: "handleNamespaces_populated_default", + existingModel: dataViewModel{ + ID: types.StringValue("space_id/dataview_id"), + SpaceID: types.StringValue("space_id"), + DataView: utils.ObjectValueFrom(ctx, &innerModel{ + ID: types.StringValue("dataview_id"), + Namespaces: utils.ListValueFrom(ctx, []string{"space_id"}, types.StringType, path.Root("data_view").AtName("namespaces"), &diags), + SourceFilters: types.ListNull(types.StringType), + FieldAttributes: types.MapNull(getFieldAttrElemType()), + RuntimeFieldMap: types.MapNull(getRuntimeFieldMapElemType()), + FieldFormats: types.MapNull(getFieldFormatElemType()), + }, getDataViewAttrTypes(), path.Root("data_view"), &diags), + }, + response: kbapi.DataViewsDataViewResponseObject{ + DataView: &kbapi.DataViewsDataViewResponseObjectInner{ + Id: utils.Pointer("dataview_id"), + Namespaces: &[]string{"space_id"}, + }, + }, + expectedModel: dataViewModel{ + ID: types.StringValue("space_id/dataview_id"), + SpaceID: types.StringValue("space_id"), + DataView: utils.ObjectValueFrom(ctx, &innerModel{ + ID: types.StringValue("dataview_id"), + Namespaces: utils.ListValueFrom(ctx, []string{"space_id"}, types.StringType, path.Root("data_view").AtName("namespaces"), &diags), + SourceFilters: types.ListNull(types.StringType), + FieldAttributes: types.MapNull(getFieldAttrElemType()), + RuntimeFieldMap: types.MapNull(getRuntimeFieldMapElemType()), + FieldFormats: types.MapNull(getFieldFormatElemType()), + }, getDataViewAttrTypes(), path.Root("data_view"), &diags), + }, + }, + { + // When sending a populated list, the response from Kibana should be the same list + name: "handleNamespaces_populated_default", + existingModel: dataViewModel{ + ID: types.StringValue("test/placeholder"), + SpaceID: types.StringValue("test"), + DataView: utils.ObjectValueFrom(ctx, &innerModel{ + ID: types.StringValue("placeholder"), + Namespaces: utils.ListValueFrom(ctx, []string{"ns1", "ns2"}, types.StringType, path.Root("data_view").AtName("namespaces"), &diags), + SourceFilters: types.ListNull(types.StringType), + FieldAttributes: types.MapNull(getFieldAttrElemType()), + RuntimeFieldMap: types.MapNull(getRuntimeFieldMapElemType()), + FieldFormats: types.MapNull(getFieldFormatElemType()), + }, getDataViewAttrTypes(), path.Root("data_view"), &diags), + }, + response: kbapi.DataViewsDataViewResponseObject{ + DataView: &kbapi.DataViewsDataViewResponseObjectInner{ + Id: utils.Pointer("placeholder"), + Namespaces: &[]string{"test", "ns1", "ns2"}, + }, + }, + expectedModel: dataViewModel{ + ID: types.StringValue("test/placeholder"), + SpaceID: types.StringValue("test"), + DataView: utils.ObjectValueFrom(ctx, &innerModel{ + ID: types.StringValue("placeholder"), + Namespaces: utils.ListValueFrom(ctx, []string{"ns1", "ns2"}, types.StringType, path.Root("data_view").AtName("namespaces"), &diags), + SourceFilters: types.ListNull(types.StringType), + FieldAttributes: types.MapNull(getFieldAttrElemType()), + RuntimeFieldMap: types.MapNull(getRuntimeFieldMapElemType()), + FieldFormats: types.MapNull(getFieldFormatElemType()), + }, getDataViewAttrTypes(), path.Root("data_view"), &diags), + }, + }, } require.Empty(t, diags) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - diags := tt.existingModel.populateFromAPI(ctx, &tt.response) + diags := tt.existingModel.populateFromAPI(ctx, &tt.response, tt.existingModel.SpaceID.ValueString()) require.Equal(t, tt.expectedModel, tt.existingModel) require.Empty(t, diags) diff --git a/internal/kibana/data_view/read.go b/internal/kibana/data_view/read.go index c2c61b906..69548a2ae 100644 --- a/internal/kibana/data_view/read.go +++ b/internal/kibana/data_view/read.go @@ -34,7 +34,7 @@ func (r *DataViewResource) Read(ctx context.Context, req resource.ReadRequest, r return } - diags = stateModel.populateFromAPI(ctx, dataView) + diags = stateModel.populateFromAPI(ctx, dataView, spaceID) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/internal/kibana/data_view/update.go b/internal/kibana/data_view/update.go index c6a912b74..07df1cde6 100644 --- a/internal/kibana/data_view/update.go +++ b/internal/kibana/data_view/update.go @@ -35,7 +35,7 @@ func (r *DataViewResource) Update(ctx context.Context, req resource.UpdateReques return } - diags = planModel.populateFromAPI(ctx, dataView) + diags = planModel.populateFromAPI(ctx, dataView, spaceID) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return