Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kibana spaces #272

Merged
merged 4 commits into from
Feb 20, 2023
Merged
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
14 changes: 7 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ jobs:
runs-on: ubuntu-latest
env:
ELASTIC_PASSWORD: password
KIBANA_USERNAME: kibana_system
KIBANA_PASSWORD: password
KIBANA_SYSTEM_USERNAME: kibana_system
KIBANA_SYSTEM_PASSWORD: password
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:${{ matrix.version }}
Expand All @@ -72,8 +72,8 @@ jobs:
env:
SERVER_NAME: kibana
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
ELASTICSEARCH_USERNAME: ${{ env.KIBANA_USERNAME }}
ELASTICSEARCH_PASSWORD: ${{ env.KIBANA_PASSWORD }}
ELASTICSEARCH_USERNAME: ${{ env.KIBANA_SYSTEM_USERNAME }}
ELASTICSEARCH_PASSWORD: ${{ env.KIBANA_SYSTEM_PASSWORD }}
ports:
- 5601:5601

Expand Down Expand Up @@ -113,8 +113,8 @@ jobs:
ELASTICSEARCH_ENDPOINTS: "http://localhost:9200"
ELASTICSEARCH_USERNAME: "elastic"
ELASTICSEARCH_PASSWORD: ${{ env.ELASTIC_PASSWORD }}
KIBANA_USERNAME: ${{ env.KIBANA_USERNAME }}
KIBANA_PASSWORD: ${{ env.KIBANA_PASSWORD }}
KIBANA_SYSTEM_USERNAME: ${{ env.KIBANA_SYSTEM_USERNAME }}
KIBANA_SYSTEM_PASSWORD: ${{ env.KIBANA_SYSTEM_PASSWORD }}

- name: TF acceptance tests
timeout-minutes: 10
Expand All @@ -125,4 +125,4 @@ jobs:
ELASTICSEARCH_ENDPOINTS: "http://localhost:9200"
ELASTICSEARCH_USERNAME: "elastic"
ELASTICSEARCH_PASSWORD: ${{ env.ELASTIC_PASSWORD }}
KIBANA_ENDPOINTS: "http://localhost:5601"
KIBANA_ENDPOINT: "http://localhost:5601"
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
deletion_protection = false
}
```
- Add support for managing Kibana spaces ([#272](https://github.com/elastic/terraform-provider-elasticstack/pull/272))

### Fixed
- Respect `ignore_unavailable` and `include_global_state` values when configuring SLM policies ([#224](https://github.com/elastic/terraform-provider-elasticstack/pull/224))
Expand Down
18 changes: 9 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ ELASTICSEARCH_NETWORK ?= elasticstack-network
ELASTICSEARCH_MEM ?= 1024m

KIBANA_NAME ?= terraform-elasticstack-kb
KIBANA_ENDPOINTS ?= http://$(KIBANA_NAME):5601
KIBANA_USERNAME ?= kibana_system
KIBANA_PASSWORD ?= password
KIBANA_ENDPOINT ?= http://$(KIBANA_NAME):5601
KIBANA_SYSTEM_USERNAME ?= kibana_system
KIBANA_SYSTEM_PASSWORD ?= password

SOURCE_LOCATION ?= $(shell pwd)

Expand All @@ -52,7 +52,6 @@ build: lint build-ci ## build the terraform provider
testacc: ## Run acceptance tests
TF_ACC=1 go test -v ./... -count $(ACCTEST_COUNT) -parallel $(ACCTEST_PARALLELISM) $(TESTARGS) -timeout $(ACCTEST_TIMEOUT)


.PHONY: test
test: ## Run unit tests
go test -v $(TEST) $(TESTARGS) -timeout=5m -parallel=4
Expand All @@ -71,7 +70,7 @@ retry = until [ $$(if [ -z "$$attempt" ]; then echo -n "0"; else echo -n "$$atte
docker-testacc: docker-elasticsearch docker-kibana ## Run acceptance tests in the docker container
@ docker run --rm \
-e ELASTICSEARCH_ENDPOINTS="$(ELASTICSEARCH_ENDPOINTS)" \
-e KIBANA_ENDPOINTS="$(KIBANA_ENDPOINTS)"
-e KIBANA_ENDPOINT="$(KIBANA_ENDPOINT)" \
-e ELASTICSEARCH_USERNAME="$(ELASTICSEARCH_USERNAME)" \
-e ELASTICSEARCH_PASSWORD="$(ELASTICSEARCH_PASSWORD)" \
--network $(ELASTICSEARCH_NETWORK) \
Expand Down Expand Up @@ -102,8 +101,9 @@ docker-kibana: docker-network docker-elasticsearch set-kibana-password ## Start
-p 5601:5601 \
-e SERVER_NAME=kibana \
-e ELASTICSEARCH_HOSTS=$(ELASTICSEARCH_ENDPOINTS) \
-e ELASTICSEARCH_USERNAME=$(KIBANA_USERNAME) \
-e ELASTICSEARCH_PASSWORD=$(KIBANA_PASSWORD) \
-e ELASTICSEARCH_USERNAME=$(KIBANA_SYSTEM_USERNAME) \
-e ELASTICSEARCH_PASSWORD=$(KIBANA_SYSTEM_PASSWORD) \
-e "logging.root.level=debug" \
--name $(KIBANA_NAME) \
--network $(ELASTICSEARCH_NETWORK) \
docker.elastic.co/kibana/kibana:$(STACK_VERSION); \
Expand All @@ -116,8 +116,8 @@ docker-network: ## Create a dedicated network for ES and test runs
fi

.PHONY: set-kibana-password
set-kibana-password: ## Sets the ES KIBANA_USERNAME's password to KIBANA_PASSWORD. This expects Elasticsearch to be available at localhost:9200
@ $(call retry, 10, curl -X POST -u $(ELASTICSEARCH_USERNAME):$(ELASTICSEARCH_PASSWORD) -H "Content-Type: application/json" http://localhost:9200/_security/user/$(KIBANA_USERNAME)/_password -d "{\"password\":\"$(KIBANA_PASSWORD)\"}" | grep -q "^{}")
set-kibana-password: ## Sets the ES KIBANA_SYSTEM_USERNAME's password to KIBANA_SYSTEM_PASSWORD. This expects Elasticsearch to be available at localhost:9200
@ $(call retry, 10, curl -X POST -u $(ELASTICSEARCH_USERNAME):$(ELASTICSEARCH_PASSWORD) -H "Content-Type: application/json" http://localhost:9200/_security/user/$(KIBANA_SYSTEM_USERNAME)/_password -d "{\"password\":\"$(KIBANA_SYSTEM_PASSWORD)\"}" | grep -q "^{}")

.PHONY: docker-clean
docker-clean: ## Try to remove provisioned nodes and assigned network
Expand Down
5 changes: 1 addition & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,9 @@ Optional:
<a id="nestedblock--kibana"></a>
### Nested Schema for `kibana`

Required:

- `endpoints` (List of String, Sensitive) A list of endpoints where the terraform provider will point to, this must include the http(s) schema and port number.

Optional:

- `endpoints` (List of String, Sensitive) A list of endpoints where the terraform provider will point to, this must include the http(s) schema and port number.
- `insecure` (Boolean) Disable TLS certificate validation
- `password` (String, Sensitive) Password to use for API authentication to Kibana.
- `username` (String) Username to use for API authentication to Kibana.
53 changes: 53 additions & 0 deletions docs/resources/kibana_space.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
subcategory: "Kibana"
layout: ""
page_title: "Elasticstack: elasticstack_kibana_space Resource"
description: |-
Creates or updates a Kibana space.
---

# Resource: elasticstack_kibana_space

Creates or updates a Kibana space. See https://www.elastic.co/guide/en/kibana/master/xpack-spaces.html

## Example Usage

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

resource "elasticstack_kibana_space" "example" {
space_id = "test_space"
name = "Test Space"
description = "A fresh space for testing visualisations"
initials = "ts"
}
```

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

### Required

- `name` (String) The display name for the space.
- `space_id` (String) The space ID that is part of the Kibana URL when inside the space.

### Optional

- `color` (String) The hexadecimal color code used in the space avatar. By default, the color is automatically generated from the space name.
- `description` (String) The description for the space.
- `disabled_features` (Set of String) The list of disabled features for the space. To get a list of available feature IDs, use the Features API (https://www.elastic.co/guide/en/kibana/master/features-api-get.html).
- `initials` (String) The initials shown in the space avatar. By default, the initials are automatically generated from the space name. Initials must be 1 or 2 characters.

### Read-Only

- `id` (String) Internal identifier of the resource.

## Import

Import is supported using the following syntax:

```shell
terraform import elasticstack_kibana_space.my_space <cluster_uuid>/<space id>
```
1 change: 1 addition & 0 deletions examples/resources/elasticstack_kibana_space/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import elasticstack_kibana_space.my_space <cluster_uuid>/<space id>
10 changes: 10 additions & 0 deletions examples/resources/elasticstack_kibana_space/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
provider "elasticstack" {
elasticsearch {}
}

resource "elasticstack_kibana_space" "example" {
space_id = "test_space"
name = "Test Space"
description = "A fresh space for testing visualisations"
initials = "ts"
}
15 changes: 10 additions & 5 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,22 @@ func init() {
}

func PreCheck(t *testing.T) {
_, endpointsOk := os.LookupEnv("ELASTICSEARCH_ENDPOINTS")
_, elasticsearchEndpointsOk := os.LookupEnv("ELASTICSEARCH_ENDPOINTS")
_, kibanaEndpointOk := os.LookupEnv("KIBANA_ENDPOINT")
_, userOk := os.LookupEnv("ELASTICSEARCH_USERNAME")
_, passOk := os.LookupEnv("ELASTICSEARCH_PASSWORD")
_, apikeyOk := os.LookupEnv("ELASTICSEARCH_API_KEY")

if !endpointsOk {
if !elasticsearchEndpointsOk {
t.Fatal("ELASTICSEARCH_ENDPOINTS must be set for acceptance tests to run")
}

if !kibanaEndpointOk {
t.Fatal("KIBANA_ENDPOINT must be set for acceptance tests to run")
}

// Technically ES tests can use the API Key, however username/password is required for Kibana tests.

Choose a reason for hiding this comment

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

👍

usernamePasswordOk := userOk && passOk
if !((!usernamePasswordOk && apikeyOk) || (usernamePasswordOk && !apikeyOk)) {
t.Fatal("Either ELASTICSEARCH_USERNAME and ELASTICSEARCH_PASSWORD must be set, or ELASTICSEARCH_API_KEY must be set for acceptance tests to run")
if !usernamePasswordOk {
t.Fatal("ELASTICSEARCH_USERNAME and ELASTICSEARCH_PASSWORD must be set for acceptance tests to run")
}
}
86 changes: 58 additions & 28 deletions internal/clients/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,36 +65,61 @@ type ApiClient struct {

func NewApiClientFunc(version string) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) {
return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
return newApiClient(d, version, true)
return newApiClient(d, version)
}
}

func NewAcceptanceTestingClient() (*ApiClient, error) {
config := elasticsearch.Config{
Header: buildHeader("tf-acceptance-testing"),
baseConfig := BaseConfig{
Header: buildHeader("tf-acceptance-testing"),
Username: os.Getenv("ELASTICSEARCH_USERNAME"),
Password: os.Getenv("ELASTICSEARCH_PASSWORD"),
}

if es := os.Getenv("ELASTICSEARCH_ENDPOINTS"); es != "" {
endpoints := make([]string, 0)
for _, e := range strings.Split(es, ",") {
endpoints = append(endpoints, strings.TrimSpace(e))
buildEsAccClient := func() (*elasticsearch.Client, error) {
config := elasticsearch.Config{
Header: baseConfig.Header,
}

if apiKey := os.Getenv("ELASTICSEARCH_API_KEY"); apiKey != "" {
config.APIKey = apiKey
} else {
config.Username = baseConfig.Username
config.Password = baseConfig.Password
}

if es := os.Getenv("ELASTICSEARCH_ENDPOINTS"); es != "" {
endpoints := make([]string, 0)
for _, e := range strings.Split(es, ",") {
endpoints = append(endpoints, strings.TrimSpace(e))
}
config.Addresses = endpoints
}
config.Addresses = endpoints

return elasticsearch.NewClient(config)
}

if username := os.Getenv("ELASTICSEARCH_USERNAME"); username != "" {
config.Username = username
config.Password = os.Getenv("ELASTICSEARCH_PASSWORD")
} else {
config.APIKey = os.Getenv("ELASTICSEARCH_API_KEY")
buildKibanaAccClient := func() (*kibana.Client, error) {
config := kibana.Config{
Username: baseConfig.Username,
Password: baseConfig.Password,
Address: os.Getenv("KIBANA_ENDPOINT"),
}

return kibana.NewClient(config)
}

es, err := elasticsearch.NewClient(config)
es, err := buildEsAccClient()
if err != nil {
return nil, err
}

kib, err := buildKibanaAccClient()
if err != nil {
return nil, err
}

return &ApiClient{es, nil, nil, "acceptance-testing"}, nil
return &ApiClient{es, nil, kib, "acceptance-testing"}, nil
}

const esConnectionKey string = "elasticsearch_connection"
Expand Down Expand Up @@ -377,7 +402,7 @@ func buildEsClient(d *schema.ResourceData, baseConfig BaseConfig, useEnvAsDefaul
return es, diags
}

func buildKibanaClient(d *schema.ResourceData, baseConfig BaseConfig, useEnvAsDefault bool) (*kibana.Client, diag.Diagnostics) {
func buildKibanaClient(d *schema.ResourceData, baseConfig BaseConfig) (*kibana.Client, diag.Diagnostics) {
var diags diag.Diagnostics

kibConn, ok := d.GetOk("kibana")
Expand All @@ -395,19 +420,20 @@ func buildKibanaClient(d *schema.ResourceData, baseConfig BaseConfig, useEnvAsDe
if kib := kibConn.([]interface{})[0]; kib != nil {
kibConfig := kib.(map[string]interface{})

if useEnvAsDefault {
if username := os.Getenv("KIBANA_USERNAME"); username != "" {
config.Username = strings.TrimSpace(username)
}
if password := os.Getenv("KIBANA_PASSWORD"); password != "" {
config.Password = strings.TrimSpace(password)
}
if username := os.Getenv("KIBANA_USERNAME"); username != "" {
config.Username = strings.TrimSpace(username)
}
if password := os.Getenv("KIBANA_PASSWORD"); password != "" {
config.Password = strings.TrimSpace(password)
}
if endpoint := os.Getenv("KIBANA_ENDPOINT"); endpoint != "" {
config.Address = endpoint
}

if username, ok := kibConfig["username"]; ok {
if username, ok := kibConfig["username"]; ok && username != "" {
config.Username = username.(string)
}
if password, ok := kibConfig["password"]; ok {
if password, ok := kibConfig["password"]; ok && password != "" {
config.Password = password.(string)
}

Expand All @@ -425,6 +451,10 @@ func buildKibanaClient(d *schema.ResourceData, baseConfig BaseConfig, useEnvAsDe

kib, err := kibana.NewClient(config)

if logging.IsDebugOrHigher() {
kib.Client.SetDebug(true)
}

if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Expand All @@ -438,16 +468,16 @@ func buildKibanaClient(d *schema.ResourceData, baseConfig BaseConfig, useEnvAsDe

const esKey string = "elasticsearch"

func newApiClient(d *schema.ResourceData, version string, useEnvAsDefault bool) (*ApiClient, diag.Diagnostics) {
func newApiClient(d *schema.ResourceData, version string) (*ApiClient, diag.Diagnostics) {
var diags diag.Diagnostics
baseConfig := buildBaseConfig(d, version, esKey)

esClient, diags := buildEsClient(d, baseConfig, useEnvAsDefault, esKey)
esClient, diags := buildEsClient(d, baseConfig, true, esKey)
if diags.HasError() {
return nil, diags
}

kibanaClient, diags := buildKibanaClient(d, baseConfig, useEnvAsDefault)
kibanaClient, diags := buildKibanaClient(d, baseConfig)
if diags.HasError() {
return nil, diags
}
Expand Down
4 changes: 2 additions & 2 deletions internal/clients/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ func (l *debugLogger) LogRoundTrip(req *http.Request, resp *http.Response, err e
}
tflog.Debug(ctx, fmt.Sprintf("%s request [%s] executed. Took %s. %#v", l.Name, requestId, duration, err))

if req != nil {
if req != nil && req.Body != nil {
l.logRequest(ctx, req, requestId)
}

if resp != nil {
if resp != nil && resp.Body != nil {
l.logResponse(ctx, resp, requestId)
}

Expand Down
Loading