Skip to content

Commit c4e9d9a

Browse files
committed
Initial Kibana dashboard resource implementation
1 parent d10f07f commit c4e9d9a

File tree

20 files changed

+118222
-0
lines changed

20 files changed

+118222
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Dashboard can be imported using the composite ID format: <space_id>/<dashboard_id>
2+
# For example, to import a dashboard with ID "my-dashboard-id" from the default space:
3+
terraform import elasticstack_kibana_dashboard.my_dashboard default/my-dashboard-id
4+
5+
# To import from a custom space:
6+
terraform import elasticstack_kibana_dashboard.my_dashboard my-space/my-dashboard-id
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
resource "elasticstack_kibana_space" "my_space" {
2+
space_id = "my-space"
3+
name = "My Space"
4+
}
5+
6+
resource "elasticstack_kibana_dashboard" "my_dashboard_in_space" {
7+
space_id = elasticstack_kibana_space.my_space.space_id
8+
title = "Dashboard in Custom Space"
9+
description = "A dashboard created in a custom Kibana space"
10+
11+
time_from = "now-24h"
12+
time_to = "now"
13+
14+
refresh_interval_pause = false
15+
refresh_interval_value = 300000 # 5 minutes
16+
17+
query_language = "kuery"
18+
query_text = ""
19+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
resource "elasticstack_kibana_dashboard" "my_dashboard" {
2+
title = "My Dashboard"
3+
description = "A dashboard showing key metrics"
4+
5+
# Time range
6+
time_from = "now-15m"
7+
time_to = "now"
8+
9+
# Refresh settings
10+
refresh_interval_pause = false
11+
refresh_interval_value = 60000 # 60 seconds
12+
13+
# Query settings with text-based query (KQL or Lucene)
14+
query_language = "kuery"
15+
query_text = "status:success"
16+
17+
# Optional tags
18+
tags = ["production", "monitoring"]
19+
}
20+
21+
# Example with JSON query (mutually exclusive with query_text)
22+
resource "elasticstack_kibana_dashboard" "my_dashboard_json" {
23+
title = "My Dashboard with JSON Query"
24+
description = "A dashboard with a structured query"
25+
26+
# Time range
27+
time_from = "now-15m"
28+
time_to = "now"
29+
30+
# Refresh settings
31+
refresh_interval_pause = false
32+
refresh_interval_value = 60000 # 60 seconds
33+
34+
# Query settings with JSON query object
35+
query_language = "kuery"
36+
query_json = jsonencode({
37+
bool = {
38+
must = [
39+
{
40+
match = {
41+
status = "success"
42+
}
43+
}
44+
]
45+
}
46+
})
47+
48+
# Optional tags
49+
tags = ["production", "monitoring"]
50+
}
51+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
resource "elasticstack_kibana_dashboard" "my_dashboard_with_options" {
2+
title = "My Dashboard with Options"
3+
description = "A dashboard with custom display options"
4+
5+
time_from = "now-1h"
6+
time_to = "now"
7+
8+
refresh_interval_pause = true
9+
refresh_interval_value = 0
10+
11+
query_language = "kuery"
12+
query_text = ""
13+
14+
# Display options
15+
options {
16+
hide_panel_titles = false
17+
use_margins = true
18+
sync_colors = true
19+
sync_tooltips = true
20+
sync_cursor = true
21+
}
22+
}

generated/kbapi/kibana.json

Lines changed: 5059 additions & 0 deletions
Large diffs are not rendered by default.

generated/kbapi/oas-with-dashboards.yaml

Lines changed: 111914 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package kibana_oapi
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
9+
"github.com/elastic/terraform-provider-elasticstack/internal/diagutil"
10+
"github.com/hashicorp/terraform-plugin-framework/diag"
11+
)
12+
13+
// buildSpaceAwarePath constructs an API path with space awareness.
14+
// If spaceID is empty or "default", returns the basePath unchanged.
15+
// Otherwise, prepends "/s/{spaceID}" to the basePath.
16+
func buildSpaceAwarePath(spaceID, basePath string) string {
17+
if spaceID != "" && spaceID != "default" {
18+
return fmt.Sprintf("/s/%s%s", spaceID, basePath)
19+
}
20+
return basePath
21+
}
22+
23+
// spaceAwarePathRequestEditor returns a RequestEditorFn that modifies the request path for space awareness.
24+
func spaceAwarePathRequestEditor(spaceID string) func(ctx context.Context, req *http.Request) error {
25+
return func(ctx context.Context, req *http.Request) error {
26+
req.URL.Path = buildSpaceAwarePath(spaceID, req.URL.Path)
27+
return nil
28+
}
29+
}
30+
31+
func addApiVersionQueryParamRequestEditor() func(ctx context.Context, req *http.Request) error {
32+
return func(ctx context.Context, req *http.Request) error {
33+
req.Header.Add("x-elastic-internal-origin", "Kibana")
34+
query := req.URL.Query()
35+
query.Add("apiVersion", "1")
36+
req.URL.RawQuery = query.Encode()
37+
return nil
38+
}
39+
}
40+
41+
// GetDashboard reads a specific dashboard from the API.
42+
func GetDashboard(ctx context.Context, client *Client, spaceID string, dashboardID string) (*kbapi.GetDashboardsDashboardIdResponse, diag.Diagnostics) {
43+
resp, err := client.API.GetDashboardsDashboardIdWithResponse(
44+
ctx, dashboardID,
45+
spaceAwarePathRequestEditor(spaceID),
46+
addApiVersionQueryParamRequestEditor(),
47+
)
48+
if err != nil {
49+
return nil, diagutil.FrameworkDiagFromError(err)
50+
}
51+
52+
switch resp.StatusCode() {
53+
case http.StatusOK:
54+
return resp, nil
55+
case http.StatusNotFound:
56+
return nil, nil
57+
default:
58+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
59+
}
60+
}
61+
62+
// CreateDashboard creates a new dashboard.
63+
func CreateDashboard(ctx context.Context, client *Client, spaceID string, req kbapi.PostDashboardsDashboardJSONRequestBody) (*kbapi.PostDashboardsDashboardResponse, diag.Diagnostics) {
64+
resp, err := client.API.PostDashboardsDashboardWithResponse(
65+
ctx,
66+
req,
67+
spaceAwarePathRequestEditor(spaceID),
68+
addApiVersionQueryParamRequestEditor(),
69+
)
70+
if err != nil {
71+
return nil, diagutil.FrameworkDiagFromError(err)
72+
}
73+
74+
switch resp.StatusCode() {
75+
case http.StatusOK:
76+
return resp, nil
77+
default:
78+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
79+
}
80+
}
81+
82+
// UpdateDashboard updates an existing dashboard.
83+
func UpdateDashboard(ctx context.Context, client *Client, spaceID string, dashboardID string, req kbapi.PutDashboardsDashboardIdJSONRequestBody) (*kbapi.PutDashboardsDashboardIdResponse, diag.Diagnostics) {
84+
resp, err := client.API.PutDashboardsDashboardIdWithResponse(
85+
ctx, dashboardID, req,
86+
spaceAwarePathRequestEditor(spaceID),
87+
addApiVersionQueryParamRequestEditor(),
88+
)
89+
if err != nil {
90+
return nil, diagutil.FrameworkDiagFromError(err)
91+
}
92+
93+
switch resp.StatusCode() {
94+
case http.StatusOK:
95+
return resp, nil
96+
default:
97+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
98+
}
99+
}
100+
101+
// DeleteDashboard deletes an existing dashboard.
102+
func DeleteDashboard(ctx context.Context, client *Client, spaceID string, dashboardID string) diag.Diagnostics {
103+
resp, err := client.API.DeleteDashboardsDashboardIdWithResponse(
104+
ctx, dashboardID,
105+
spaceAwarePathRequestEditor(spaceID),
106+
addApiVersionQueryParamRequestEditor(),
107+
)
108+
if err != nil {
109+
return diagutil.FrameworkDiagFromError(err)
110+
}
111+
112+
switch resp.StatusCode() {
113+
case http.StatusOK, http.StatusNoContent:
114+
return nil
115+
case http.StatusNotFound:
116+
return nil
117+
default:
118+
return reportUnknownError(resp.StatusCode(), resp.Body)
119+
}
120+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package dashboard_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
7+
"github.com/elastic/terraform-provider-elasticstack/internal/versionutils"
8+
"github.com/hashicorp/go-version"
9+
"github.com/hashicorp/terraform-plugin-testing/config"
10+
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
11+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
12+
)
13+
14+
// Dashboard API is in technical preview and available from 9.2.x onwards
15+
var minDashboardAPISupport = version.Must(version.NewVersion("9.2.0"))
16+
17+
func TestAccResourceEmptyDashboard(t *testing.T) {
18+
dashboardTitle := "Test Dashboard " + sdkacctest.RandStringFromCharSet(4, sdkacctest.CharSetAlphaNum)
19+
20+
resource.Test(t, resource.TestCase{
21+
PreCheck: func() { acctest.PreCheck(t) },
22+
Steps: []resource.TestStep{
23+
{
24+
ProtoV6ProviderFactories: acctest.Providers,
25+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minDashboardAPISupport),
26+
ConfigDirectory: acctest.NamedTestCaseDirectory("basic"),
27+
ConfigVariables: config.Variables{
28+
"dashboard_title": config.StringVariable(dashboardTitle),
29+
},
30+
Check: resource.ComposeTestCheckFunc(
31+
resource.TestCheckResourceAttrSet("elasticstack_kibana_dashboard.test", "id"),
32+
resource.TestCheckResourceAttrSet("elasticstack_kibana_dashboard.test", "dashboard_id"),
33+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "title", dashboardTitle),
34+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "description", "Test dashboard description"),
35+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "time_from", "now-15m"),
36+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "time_to", "now"),
37+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "refresh_interval_pause", "true"),
38+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "refresh_interval_value", "90000"),
39+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "query_language", "kuery"),
40+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "query_text", ""),
41+
),
42+
},
43+
{
44+
ProtoV6ProviderFactories: acctest.Providers,
45+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minDashboardAPISupport),
46+
ConfigDirectory: acctest.NamedTestCaseDirectory("updated"),
47+
ConfigVariables: config.Variables{
48+
"dashboard_title": config.StringVariable(dashboardTitle + " Updated"),
49+
},
50+
Check: resource.ComposeTestCheckFunc(
51+
resource.TestCheckResourceAttrSet("elasticstack_kibana_dashboard.test", "id"),
52+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "title", dashboardTitle+" Updated"),
53+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "description", "Updated dashboard description"),
54+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "time_from", "now-30m"),
55+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "time_to", "now"),
56+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "refresh_interval_pause", "false"),
57+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "refresh_interval_value", "30000"),
58+
),
59+
},
60+
{
61+
ProtoV6ProviderFactories: acctest.Providers,
62+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minDashboardAPISupport),
63+
ConfigDirectory: acctest.NamedTestCaseDirectory("with_options"),
64+
ConfigVariables: config.Variables{
65+
"dashboard_title": config.StringVariable(dashboardTitle + " with Options"),
66+
},
67+
Check: resource.ComposeTestCheckFunc(
68+
resource.TestCheckResourceAttrSet("elasticstack_kibana_dashboard.test", "id"),
69+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "title", dashboardTitle+" with Options"),
70+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "time_from", "2024-01-01T00:00:00.000Z"),
71+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "time_to", "2024-01-01T01:00:00.000Z"),
72+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "time_range_mode", "absolute"),
73+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "options.hide_panel_titles", "true"),
74+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "options.use_margins", "false"),
75+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "options.sync_colors", "true"),
76+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test", "options.sync_tooltips", "true"),
77+
),
78+
},
79+
{
80+
ProtoV6ProviderFactories: acctest.Providers,
81+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minDashboardAPISupport),
82+
ConfigDirectory: acctest.NamedTestCaseDirectory("with_options"),
83+
ConfigVariables: config.Variables{
84+
"dashboard_title": config.StringVariable(dashboardTitle + " with Options"),
85+
},
86+
ResourceName: "elasticstack_kibana_dashboard.test",
87+
ImportState: true,
88+
ImportStateVerify: true,
89+
ImportStateVerifyIgnore: []string{"time_range_mode"},
90+
},
91+
},
92+
})
93+
}
94+
95+
func TestAccResourceDashboardInSpace(t *testing.T) {
96+
spaceName := "test-space-" + sdkacctest.RandStringFromCharSet(4, sdkacctest.CharSetAlphaNum)
97+
dashboardTitle := "Test Dashboard in Space " + sdkacctest.RandStringFromCharSet(4, sdkacctest.CharSetAlphaNum)
98+
99+
resource.Test(t, resource.TestCase{
100+
PreCheck: func() { acctest.PreCheck(t) },
101+
Steps: []resource.TestStep{
102+
{
103+
ProtoV6ProviderFactories: acctest.Providers,
104+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minDashboardAPISupport),
105+
ConfigDirectory: acctest.NamedTestCaseDirectory("in_space"),
106+
ConfigVariables: config.Variables{
107+
"space_name": config.StringVariable(spaceName),
108+
"dashboard_title": config.StringVariable(dashboardTitle),
109+
},
110+
Check: resource.ComposeTestCheckFunc(
111+
resource.TestCheckResourceAttrSet("elasticstack_kibana_dashboard.test_space", "id"),
112+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test_space", "space_id", spaceName),
113+
resource.TestCheckResourceAttr("elasticstack_kibana_dashboard.test_space", "title", dashboardTitle),
114+
),
115+
},
116+
{
117+
ProtoV6ProviderFactories: acctest.Providers,
118+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minDashboardAPISupport),
119+
ConfigDirectory: acctest.NamedTestCaseDirectory("in_space"),
120+
ConfigVariables: config.Variables{
121+
"space_name": config.StringVariable(spaceName),
122+
"dashboard_title": config.StringVariable(dashboardTitle),
123+
},
124+
ResourceName: "elasticstack_kibana_dashboard.test_space",
125+
ImportState: true,
126+
ImportStateVerify: true,
127+
},
128+
},
129+
})
130+
}

0 commit comments

Comments
 (0)