diff --git a/CHANGELOG.md b/CHANGELOG.md index a14b5cc7e..c075572b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Add support for data_stream `lifecycle` template settings ([#724](https://github.com/elastic/terraform-provider-elasticstack/pull/724)) - Fix a provider panic when `elasticstack_kibana_action_connector` reads a non-existant connector ([#729](https://github.com/elastic/terraform-provider-elasticstack/pull/729)) - Add support for `remote_indicies` to `elasticstack_elasticsearch_security_role` & `elasticstack_kibana_security_role` (#723)[https://github.com/elastic/terraform-provider-elasticstack/pull/723] +- Fix error handling in `elasticstack_kibana_import_saved_objects` ([#738](https://github.com/elastic/terraform-provider-elasticstack/pull/738)) ## [0.11.6] - 2024-08-20 diff --git a/internal/kibana/import_saved_objects/acc_test.go b/internal/kibana/import_saved_objects/acc_test.go index d2f89b608..3ee48f346 100644 --- a/internal/kibana/import_saved_objects/acc_test.go +++ b/internal/kibana/import_saved_objects/acc_test.go @@ -30,6 +30,16 @@ func TestAccResourceImportSavedObjects(t *testing.T) { resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.settings", "errors.#", "0"), ), }, + { + // Ensure a partially successful import doesn't throw a provider error + Config: testAccResourceImportSavedObjectsMissingRef(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.settings", "success", "false"), + resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.settings", "success_count", "1"), + resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.settings", "success_results.#", "1"), + resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.settings", "errors.#", "1"), + ), + }, }, }) } @@ -67,3 +77,20 @@ EOT } ` } + +func testAccResourceImportSavedObjectsMissingRef() string { + return ` +provider "elasticstack" { + elasticsearch {} + kibana {} +} + +resource "elasticstack_kibana_import_saved_objects" "settings" { + file_contents = <<-EOT +{"attributes":{"buildNum":42747,"defaultIndex":"metricbeat-*","theme:darkMode":true},"coreMigrationVersion":"7.0.0","id":"7.14.0","managed":false,"references":[],"type":"config","typeMigrationVersion":"7.0.0","updated_at":"2021-08-04T02:04:43.306Z","version":"WzY1MiwyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"testName\",\"negate\":false,\"params\":{\"query\":\"perf-features\"},\"type\":\"phrase\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"match_phrase\":{\"testName\":\"perf-features\"}}},{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"status\",\"negate\":false,\"params\":{\"query\":\"failed\"},\"type\":\"phrase\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index\"},\"query\":{\"match_phrase\":{\"status\":\"failed\"}}},{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"targetEnvironment\",\"negate\":false,\"params\":{\"query\":\"dev\"},\"type\":\"phrase\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[2].meta.index\"},\"query\":{\"match_phrase\":{\"targetEnvironment\":\"dev\"}}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"healthchecks","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"healthchecks\",\"type\":\"histogram\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"params\":{\"field\":\"@timestamp\",\"timeRange\":{\"from\":\"now/d\",\"to\":\"now/d\"},\"useNormalizedEsInterval\":true,\"scaleMetricValues\":false,\"interval\":\"30m\",\"drop_partials\":false,\"min_doc_count\":1,\"extended_bounds\":{}},\"schema\":\"segment\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"testName\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"normal\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":true,\"labels\":{\"show\":false},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"legendSize\":\"auto\"}}"},"coreMigrationVersion":"8.8.0","id":"bacba650-0c8e-11eb-977b-cd2574857abe","managed":false,"references":[{"id":"5ba9aac0-0c7e-11ea-b151-954d48d0eae6","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"5ba9aac0-0c7e-11ea-b151-954d48d0eae6","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index","type":"index-pattern"},{"id":"5ba9aac0-0c7e-11ea-b151-954d48d0eae6","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index","type":"index-pattern"},{"id":"5ba9aac0-0c7e-11ea-b151-954d48d0eae6","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[2].meta.index","type":"index-pattern"}],"type":"visualization","typeMigrationVersion":"8.5.0","updated_at":"2020-10-12T13:27:52.373Z","version":"WzE2NjgsMl0="} +{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":22,"missingRefCount":1,"missingReferences":[{"id":"5ba9aac0-0c7e-11ea-b151-954d48d0eae6","type":"index-pattern"}]} +EOT + overwrite = true +}` +} diff --git a/internal/kibana/import_saved_objects/create.go b/internal/kibana/import_saved_objects/create.go index 358a06bff..4dad4f434 100644 --- a/internal/kibana/import_saved_objects/create.go +++ b/internal/kibana/import_saved_objects/create.go @@ -2,6 +2,8 @@ package import_saved_objects import ( "context" + "fmt" + "strings" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -71,7 +73,23 @@ func (r *Resource) importObjects(ctx context.Context, plan tfsdk.Plan, state *tf } if !respModel.Success && !model.IgnoreImportErrors.ValueBool() { - diags.AddError("not all objects were imported successfully", "see errors attribute for more details") + var detail strings.Builder + for i, err := range respModel.Errors { + detail.WriteString(fmt.Sprintf("import error [%d]: %s\n", i, err)) + } + detail.WriteString("see the `errors` attribute for the full resposne") + + if respModel.SuccessCount > 0 { + diags.AddWarning( + "not all objects were imported successfully", + detail.String(), + ) + } else { + diags.AddError( + "no objects imported successfully", + detail.String(), + ) + } } } @@ -90,15 +108,24 @@ type importSuccess struct { } type importError struct { - ID string `json:"id"` - Type string `json:"type"` - Title string `json:"title"` - Error importErrorType `json:"error"` - Meta importMeta `json:"meta"` + ID string `tfsdk:"id" json:"id"` + Type string `tfsdk:"type" json:"type"` + Title string `tfsdk:"title" json:"title"` + Error importErrorType `tfsdk:"error" json:"error"` + Meta importMeta `tfsdk:"meta" json:"meta"` +} + +func (ie importError) String() string { + title := ie.Title + if title == "" { + title = ie.Meta.Title + } + + return fmt.Sprintf("[%s] error on [%s] with ID [%s] and title [%s]", ie.Error.Type, ie.Type, ie.ID, title) } type importErrorType struct { - Type string `json:"type"` + Type string `tfsdk:"type" json:"type"` } type importMeta struct {