Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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 `elasticstack_kibana_export_saved_objects` data source ([#1293](https://github.com/elastic/terraform-provider-elasticstack/pull/1293))
- Create `elasticstack_kibana_maintenance_window` resource. ([#1224](https://github.com/elastic/terraform-provider-elasticstack/pull/1224))
- Add support for `solution` field in `elasticstack_kibana_space` resource and data source ([#1102](https://github.com/elastic/terraform-provider-elasticstack/issues/1102))
- Add `slo_id` validation to `elasticstack_kibana_slo` ([#1221](https://github.com/elastic/terraform-provider-elasticstack/pull/1221))
Expand Down
54 changes: 54 additions & 0 deletions docs/data-sources/kibana_export_saved_objects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "elasticstack_kibana_export_saved_objects Data Source - elasticstack"
subcategory: ""
description: |-
Export Kibana saved objects. This data source allows you to export saved objects from Kibana and store the result in the Terraform state.
---

# elasticstack_kibana_export_saved_objects (Data Source)

Export Kibana saved objects. This data source allows you to export saved objects from Kibana and store the result in the Terraform state.

## Example Usage

```terraform
provider "elasticstack" {
elasticsearch {}
kibana {}
}

data "elasticstack_kibana_export_saved_objects" "example" {
space_id = "default"
exclude_export_details = true
include_references_deep = true
objects = jsonencode([
{
"type" : "dashboard",
"id" : "7c5f07ee-7e41-4d50-ae1f-dfe54cc87208"
}
])
}

output "saved_objects" {
value = data.elasticstack_kibana_export_saved_objects.example.exported_objects
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `objects` (String) JSON-encoded list of objects to export. Each object should have 'type' and 'id' fields.

### Optional

- `exclude_export_details` (Boolean) Do not add export details. Defaults to true.
- `include_references_deep` (Boolean) Include references to other saved objects recursively. Defaults to true.
- `space_id` (String) An identifier for the space. If space_id is not provided, the default space is used.

### Read-Only

- `exported_objects` (String) The exported objects in NDJSON format.
- `id` (String) Generated ID for the export.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
provider "elasticstack" {
elasticsearch {}
kibana {}
}

data "elasticstack_kibana_export_saved_objects" "example" {
space_id = "default"
exclude_export_details = true
include_references_deep = true
objects = jsonencode([
{
"type" : "dashboard",
"id" : "7c5f07ee-7e41-4d50-ae1f-dfe54cc87208"
}
])
}

output "saved_objects" {
value = data.elasticstack_kibana_export_saved_objects.example.exported_objects
}
54 changes: 54 additions & 0 deletions internal/kibana/export_saved_objects/acc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package export_saved_objects_test

import (
"testing"

"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAccDataSourceKibanaExportSavedObjects(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccDataSourceKibanaExportSavedObjectsConfig,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.elasticstack_kibana_export_saved_objects.test", "id"),
resource.TestCheckResourceAttrSet("data.elasticstack_kibana_export_saved_objects.test", "exported_objects"),
resource.TestCheckResourceAttr("data.elasticstack_kibana_export_saved_objects.test", "space_id", "default"),
resource.TestCheckResourceAttr("data.elasticstack_kibana_export_saved_objects.test", "exclude_export_details", "true"),
resource.TestCheckResourceAttr("data.elasticstack_kibana_export_saved_objects.test", "include_references_deep", "true"),
),
},
},
})
}

const testAccDataSourceKibanaExportSavedObjectsConfig = `
provider "elasticstack" {
elasticsearch {}
kibana {}
}

resource "elasticstack_kibana_action_connector" "test" {
name = "test-export-connector"
connector_type_id = ".slack"
secrets = jsonencode({
webhookUrl = "https://example.com"
})
}

data "elasticstack_kibana_export_saved_objects" "test" {
space_id = "default"
exclude_export_details = true
include_references_deep = true
objects = jsonencode([
{
"type": "action",
"id": elasticstack_kibana_action_connector.test.connector_id
}
])
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
objects = jsonencode([
{
"type": "action",
"id": elasticstack_kibana_action_connector.test.connector_id
}
])
objects = [
{
type = "action",
id = elasticstack_kibana_action_connector.test.connector_id
}
]

}
`
46 changes: 46 additions & 0 deletions internal/kibana/export_saved_objects/data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package export_saved_objects

import (
"context"

"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/hashicorp/terraform-plugin-framework/datasource"
)

// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &dataSource{}
_ datasource.DataSourceWithConfigure = &dataSource{}
)

// NewDataSource is a helper function to simplify the provider implementation.
func NewDataSource() datasource.DataSource {
return &dataSource{}
}

// dataSource is the data source implementation.
type dataSource struct {
client *clients.ApiClient
}

// Metadata returns the data source type name.
func (d *dataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_kibana_export_saved_objects"
}

// Configure adds the provider configured client to the data source.
func (d *dataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Add a nil check when handling ProviderData because Terraform
// sets that data after it calls the ConfigureProvider RPC.
if req.ProviderData == nil {
return
}

client, diags := clients.ConvertProviderData(req.ProviderData)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

d.client = client
}
117 changes: 117 additions & 0 deletions internal/kibana/export_saved_objects/read.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package export_saved_objects

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// Read refreshes the Terraform state with the latest data.
func (d *dataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var config dataSourceModel

// Read configuration
diags := req.Config.Get(ctx, &config)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

// Get Kibana client
oapiClient, err := d.client.GetKibanaOapiClient()
if err != nil {
resp.Diagnostics.AddError("unable to get Kibana client", err.Error())
return
}

// Set default space_id if not provided
spaceId := "default"
if !config.SpaceID.IsNull() && !config.SpaceID.IsUnknown() {
spaceId = config.SpaceID.ValueString()
}

// Parse objects JSON
var objectsList kbapi.PostSavedObjectsExportJSONBodyHasReference1
objectsJSON := config.Objects.ValueString()

var rawObjects []map[string]interface{}
if err := json.Unmarshal([]byte(objectsJSON), &rawObjects); err != nil {
resp.Diagnostics.AddError("Invalid objects JSON", fmt.Sprintf("Error parsing objects JSON: %v", err))
return
}

for _, obj := range rawObjects {
id, ok := obj["id"].(string)
if !ok {
resp.Diagnostics.AddError("Invalid object", "Object missing 'id' field")
return
}
objType, ok := obj["type"].(string)
if !ok {
resp.Diagnostics.AddError("Invalid object", "Object missing 'type' field")
return
}
objectsList = append(objectsList, struct {
Id string `json:"id"`
Type string `json:"type"`
}{
Id: id,
Type: objType,
})
}
Copy link
Member

Choose a reason for hiding this comment

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

Assuming the schema change above

Suggested change
var objectsList kbapi.PostSavedObjectsExportJSONBodyHasReference1
objectsJSON := config.Objects.ValueString()
var rawObjects []map[string]interface{}
if err := json.Unmarshal([]byte(objectsJSON), &rawObjects); err != nil {
resp.Diagnostics.AddError("Invalid objects JSON", fmt.Sprintf("Error parsing objects JSON: %v", err))
return
}
for _, obj := range rawObjects {
id, ok := obj["id"].(string)
if !ok {
resp.Diagnostics.AddError("Invalid object", "Object missing 'id' field")
return
}
objType, ok := obj["type"].(string)
if !ok {
resp.Diagnostics.AddError("Invalid object", "Object missing 'type' field")
return
}
objectsList = append(objectsList, struct {
Id string `json:"id"`
Type string `json:"type"`
}{
Id: id,
Type: objType,
})
}
objectsList := utils.ListTypeToSlice(ctx, config.Objects, path.Root("objects"), &resp.Diagnostics, func(item objectModel, meta utils.ListMeta) struct {
Id string `json:"id"`
Type string `json:"type"`
} {
return struct {
Id string `json:"id"`
Type string `json:"type"`
}{
Id: item.ID.ValueString(),
Type: item.Type.ValueString(),
}
})


// Set default values for boolean options
excludeExportDetails := true
if !config.ExcludeExportDetails.IsNull() && !config.ExcludeExportDetails.IsUnknown() {
excludeExportDetails = config.ExcludeExportDetails.ValueBool()
}

includeReferencesDeep := true
if !config.IncludeReferencesDeep.IsNull() && !config.IncludeReferencesDeep.IsUnknown() {
includeReferencesDeep = config.IncludeReferencesDeep.ValueBool()
}

// Create request body
body := kbapi.PostSavedObjectsExportJSONRequestBody{
ExcludeExportDetails: &excludeExportDetails,
IncludeReferencesDeep: &includeReferencesDeep,
Objects: &objectsList,
}

// Make the API call
apiResp, err := oapiClient.API.PostSavedObjectsExportWithResponse(ctx, body)
if err != nil {
resp.Diagnostics.AddError("API call failed", fmt.Sprintf("Unable to export saved objects: %v", err))
return
}

if apiResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected API response",
fmt.Sprintf("Unexpected status code from server: got HTTP %d, response: %s", apiResp.StatusCode(), string(apiResp.Body)),
)
return
}

// Create composite ID for state tracking
compositeID := &clients.CompositeId{ClusterId: spaceId, ResourceId: "export"}

// Set the state
var state dataSourceModel
state.ID = types.StringValue(compositeID.String())
state.SpaceID = types.StringValue(spaceId)
state.Objects = config.Objects
state.ExcludeExportDetails = types.BoolValue(excludeExportDetails)
state.IncludeReferencesDeep = types.BoolValue(includeReferencesDeep)
state.ExportedObjects = types.StringValue(string(apiResp.Body))

// Set state
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
}
52 changes: 52 additions & 0 deletions internal/kibana/export_saved_objects/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package export_saved_objects

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// Schema defines the schema for the data source.
func (d *dataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Export Kibana saved objects. This data source allows you to export saved objects from Kibana and store the result in the Terraform state.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Generated ID for the export.",
Computed: true,
},
"space_id": schema.StringAttribute{
Description: "An identifier for the space. If space_id is not provided, the default space is used.",
Optional: true,
},
"objects": schema.StringAttribute{
Description: "JSON-encoded list of objects to export. Each object should have 'type' and 'id' fields.",
Required: 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
"objects": schema.StringAttribute{
Description: "JSON-encoded list of objects to export. Each object should have 'type' and 'id' fields.",
Required: true,
},
"objects": schema.ListNestedAttribute{
Description: "List of objects to export.",
Required: true,
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
},
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"type": schema.StringAttribute{
Description: "The type of the saved object.",
Required: true,
},
"id": schema.StringAttribute{
Description: "The ID of the saved object.",
Required: true,
},
},
},
},

We can make this much simpler for users by fully defining the input schema here.

"exclude_export_details": schema.BoolAttribute{
Description: "Do not add export details. Defaults to true.",
Optional: true,
},
"include_references_deep": schema.BoolAttribute{
Description: "Include references to other saved objects recursively. Defaults to true.",
Optional: true,
},
"exported_objects": schema.StringAttribute{
Description: "The exported objects in NDJSON format.",
Computed: true,
},
},
}
}

// dataSourceModel maps the data source schema data.
type dataSourceModel struct {
ID types.String `tfsdk:"id"`
SpaceID types.String `tfsdk:"space_id"`
Objects types.String `tfsdk:"objects"`
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
Objects types.String `tfsdk:"objects"`
Objects types.List `tfsdk:"objects"`

ExcludeExportDetails types.Bool `tfsdk:"exclude_export_details"`
IncludeReferencesDeep types.Bool `tfsdk:"include_references_deep"`
ExportedObjects types.String `tfsdk:"exported_objects"`
}
Copy link
Member

Choose a reason for hiding this comment

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

Assuming the schema change above

Suggested change
}
}
type objectModel struct {
Type types.String `tfsdk:"type"`
ID types.String `tfsdk:"id"`
}

2 changes: 2 additions & 0 deletions provider/plugin_framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/fleet/output"
"github.com/elastic/terraform-provider-elasticstack/internal/fleet/server_host"
"github.com/elastic/terraform-provider-elasticstack/internal/kibana/data_view"
"github.com/elastic/terraform-provider-elasticstack/internal/kibana/export_saved_objects"
"github.com/elastic/terraform-provider-elasticstack/internal/kibana/import_saved_objects"
"github.com/elastic/terraform-provider-elasticstack/internal/kibana/maintenance_window"
"github.com/elastic/terraform-provider-elasticstack/internal/kibana/spaces"
Expand Down Expand Up @@ -87,6 +88,7 @@ func (p *Provider) DataSources(ctx context.Context) []func() datasource.DataSour
return []func() datasource.DataSource{
indices.NewDataSource,
spaces.NewDataSource,
export_saved_objects.NewDataSource,
enrollment_tokens.NewDataSource,
integration_ds.NewDataSource,
enrich.NewEnrichPolicyDataSource,
Expand Down