Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [Unreleased]

- Add `file_contents_wo` and `file_contents_wo_version` write-only attributes to `elasticstack_kibana_import_saved_objects` resource as alternatives to `file_contents`
- Fix `elasticstack_elasticsearch_snapshot_lifecycle` metadata type conversion causing terraform apply to fail ([#1409](https://github.com/elastic/terraform-provider-elasticstack/issues/1409))
- Add new `elasticstack_elasticsearch_ml_anomaly_detection_job` resource ([#1329](https://github.com/elastic/terraform-provider-elasticstack/pull/1329))
- Add new `elasticstack_elasticsearch_ml_datafeed` resource ([1340](https://github.com/elastic/terraform-provider-elasticstack/pull/1340))
Expand Down
7 changes: 3 additions & 4 deletions docs/resources/kibana_import_saved_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ EOT
<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `file_contents` (String) The contents of the exported saved objects file.

### Optional

- `file_contents` (String) The contents of the exported saved objects file.
- `file_contents_wo` (String, Sensitive) The contents of the exported saved objects file (write-only, not stored in state).
- `file_contents_wo_version` (String) Version or identifier for the file contents (write-only, not stored in state).
- `ignore_import_errors` (Boolean) If set to true, errors during the import process will not fail the configuration application
- `overwrite` (Boolean) Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object.
- `space_id` (String) An identifier for the space. If space_id is not provided, the default space is used.
Expand Down
137 changes: 137 additions & 0 deletions internal/kibana/import_saved_objects/acc_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package import_saved_objects_test

import (
"regexp"
"testing"

"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
Expand Down Expand Up @@ -94,3 +95,139 @@ EOT
overwrite = true
}`
}

func TestAccResourceImportSavedObjectsWriteOnly(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccResourceImportSavedObjectsWriteOnly(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.wo_test", "success", "true"),
resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.wo_test", "success_count", "1"),
resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.wo_test", "success_results.#", "1"),
resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.wo_test", "errors.#", "0"),
),
},
},
})
}

func testAccResourceImportSavedObjectsWriteOnly() string {
return `
provider "elasticstack" {
elasticsearch {}
kibana {}
}

resource "elasticstack_kibana_import_saved_objects" "wo_test" {
overwrite = true
file_contents_wo = <<-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=="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
EOT
}
`
}

func TestAccResourceImportSavedObjectsWriteOnlyWithVersion(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccResourceImportSavedObjectsWriteOnlyWithVersion(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.wo_version_test", "success", "true"),
resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.wo_version_test", "success_count", "1"),
resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.wo_version_test", "success_results.#", "1"),
resource.TestCheckResourceAttr("elasticstack_kibana_import_saved_objects.wo_version_test", "errors.#", "0"),
),
},
},
})
}

func testAccResourceImportSavedObjectsWriteOnlyWithVersion() string {
return `
provider "elasticstack" {
elasticsearch {}
kibana {}
}

resource "elasticstack_kibana_import_saved_objects" "wo_version_test" {
overwrite = true
file_contents_wo = <<-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=="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
EOT
file_contents_wo_version = "1"
}
`
}

func TestAccResourceImportSavedObjectsConflictValidation(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccResourceImportSavedObjectsConflict(),
ExpectError: regexp.MustCompile(`cannot be specified when`),
},
},
})
}

func testAccResourceImportSavedObjectsConflict() string {
return `
provider "elasticstack" {
elasticsearch {}
kibana {}
}

resource "elasticstack_kibana_import_saved_objects" "conflict_test" {
overwrite = true
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=="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
EOT
file_contents_wo = <<-EOT
{"attributes":{"buildNum":42747,"defaultIndex":"metricbeat-*","theme:darkMode":false},"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=="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
EOT
}
`
}

func TestAccResourceImportSavedObjectsDependencyValidation(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccResourceImportSavedObjectsDependency(),
ExpectError: regexp.MustCompile(`Attribute "file_contents_wo" must be specified when\s+"file_contents_wo_version" is specified`),
},
},
})
}

func testAccResourceImportSavedObjectsDependency() string {
return `
provider "elasticstack" {
elasticsearch {}
kibana {}
}

resource "elasticstack_kibana_import_saved_objects" "dependency_test" {
overwrite = true
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=="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
EOT
file_contents_wo_version = "v1.0.0"
}
`
}
19 changes: 16 additions & 3 deletions internal/kibana/import_saved_objects/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import (
)

func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
r.importObjects(ctx, request.Plan, &response.State, &response.Diagnostics)
r.importObjects(ctx, request.Plan, request.Config, &response.State, &response.Diagnostics)
}

func (r *Resource) importObjects(ctx context.Context, plan tfsdk.Plan, state *tfsdk.State, diags *diag.Diagnostics) {
func (r *Resource) importObjects(ctx context.Context, plan tfsdk.Plan, config tfsdk.Config, state *tfsdk.State, diags *diag.Diagnostics) {
var model modelV0

diags.Append(plan.Get(ctx, &model)...)
Expand All @@ -32,7 +32,20 @@ func (r *Resource) importObjects(ctx context.Context, plan tfsdk.Plan, state *tf
return
}

resp, err := kibanaClient.KibanaSavedObject.Import([]byte(model.FileContents.ValueString()), model.Overwrite.ValueBool(), model.SpaceID.ValueString())
// Determine which file contents to use (file_contents or file_contents_wo)
// Read write-only attributes from config as per Terraform best practices
var fileContentsWO types.String
diags.Append(config.GetAttribute(ctx, path.Root("file_contents_wo"), &fileContentsWO)...)
if diags.HasError() {
return
}

fileContents := model.FileContents.ValueString()
if !fileContentsWO.IsNull() && !fileContentsWO.IsUnknown() {
fileContents = fileContentsWO.ValueString()
}

resp, err := kibanaClient.KibanaSavedObject.Import([]byte(fileContents), model.Overwrite.ValueBool(), model.SpaceID.ValueString())
if err != nil {
diags.AddError("failed to import saved objects", err.Error())
return
Expand Down
48 changes: 40 additions & 8 deletions internal/kibana/import_saved_objects/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@ import (
"context"

"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// Ensure provider defined types fully satisfy framework interfaces
var _ resource.Resource = &Resource{}
var _ resource.ResourceWithConfigure = &Resource{}
var _ resource.ResourceWithConfigValidators = &Resource{}

// TODO - Uncomment these lines when we're using a kibana client which supports create_new_copies and compatibility_mode
// create_new_copies and compatibility_mode aren't supported by the current version of the Kibana client
// We can add these ourselves once https://github.com/elastic/terraform-provider-elasticstack/pull/372 is merged

// var _ resource.ResourceWithConfigValidators = &Resource{}

// func (r *Resource) ConfigValidators(context.Context) []resource.ConfigValidator {
// return []resource.ConfigValidator{
// resourcevalidator.Conflicting(
Expand All @@ -32,6 +35,15 @@ var _ resource.ResourceWithConfigure = &Resource{}
// }
// }

func (r *Resource) ConfigValidators(context.Context) []resource.ConfigValidator {
return []resource.ConfigValidator{
resourcevalidator.AtLeastOneOf(
path.MatchRoot("file_contents"),
path.MatchRoot("file_contents_wo"),
),
}
}

func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Create sets of Kibana saved objects from a file created by the export API. See https://www.elastic.co/guide/en/kibana/current/saved-objects-api-import.html",
Expand Down Expand Up @@ -67,7 +79,25 @@ func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *res
// },
"file_contents": schema.StringAttribute{
Description: "The contents of the exported saved objects file.",
Required: true,
Optional: true,
Validators: []validator.String{
stringvalidator.ConflictsWith(path.MatchRoot("file_contents_wo")),
},
},
"file_contents_wo": schema.StringAttribute{
Description: "The contents of the exported saved objects file (write-only, not stored in state).",
Optional: true,
Sensitive: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Sensitive: true,

It's not a sensitive value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed Sensitive flag in commit 35a1c9c

Validators: []validator.String{
stringvalidator.ConflictsWith(path.MatchRoot("file_contents")),
},
},
"file_contents_wo_version": schema.StringAttribute{
Description: "Version or identifier for the file contents (write-only, not stored in state).",
Optional: true,
Validators: []validator.String{
stringvalidator.AlsoRequires(path.MatchRoot("file_contents_wo")),
},
},

"success": schema.BoolAttribute{
Expand Down Expand Up @@ -140,9 +170,11 @@ type modelV0 struct {
// CreateNewCopies types.Bool `tfsdk:"create_new_copies"`
Overwrite types.Bool `tfsdk:"overwrite"`
// CompatibilityMode types.Bool `tfsdk:"compatibility_mode"`
FileContents types.String `tfsdk:"file_contents"`
Success types.Bool `tfsdk:"success"`
SuccessCount types.Int64 `tfsdk:"success_count"`
Errors types.List `tfsdk:"errors"`
SuccessResults types.List `tfsdk:"success_results"`
FileContents types.String `tfsdk:"file_contents"`
FileContentsWO types.String `tfsdk:"file_contents_wo"`
FileContentsWOVersion types.String `tfsdk:"file_contents_wo_version"`
Success types.Bool `tfsdk:"success"`
SuccessCount types.Int64 `tfsdk:"success_count"`
Errors types.List `tfsdk:"errors"`
SuccessResults types.List `tfsdk:"success_results"`
}
2 changes: 1 addition & 1 deletion internal/kibana/import_saved_objects/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import (
)

func (r *Resource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
r.importObjects(ctx, request.Plan, &response.State, &response.Diagnostics)
r.importObjects(ctx, request.Plan, request.Config, &response.State, &response.Diagnostics)
}
Loading