Skip to content

Commit

Permalink
Kibana spaces (#272)
Browse files Browse the repository at this point in the history
* Fixup kibana client construction

* Add support for managing Kibana spaces

* Docs

* Changelog
  • Loading branch information
tobio authored Feb 20, 2023
1 parent c3f69c9 commit e662d3f
Show file tree
Hide file tree
Showing 15 changed files with 477 additions and 57 deletions.
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.
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

0 comments on commit e662d3f

Please sign in to comment.