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

ec_deployment_elasticsearch_keystore: Add resource #364

93 changes: 93 additions & 0 deletions docs/resources/ec_deployment_elasticsearch_keystore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
page_title: "Elastic Cloud: ec_deployment_elasticsearch_keystore"
description: |-
Provides an Elastic Cloud Deployment Elasticsearch keystore resource, which allows creating and updating Elasticsearch Keystore settings.
---

# Resource: ec_deployment_elasticsearch_keystore
Provides an Elastic Cloud Deployment Elasticsearch keystore resource, which allows creating and updating Elasticsearch keystore settings.
marclop marked this conversation as resolved.
Show resolved Hide resolved

Elasticsearch keystore settings can be created and updated through this resource, **each resource represents a single Elasticsearch Keystore setting**. After adding a key and its secret value to the keystore, you can use the key in place of the secret value when you configure sensitive settings.

~> **Note on Elastic keystore settings** This resource offers weaker consistency guarantees and will not detect and update keystore setting values that have been modified outside of the scope of Terraform, usually referred to as _drift_. For instance, examine the following scenario:
marclop marked this conversation as resolved.
Show resolved Hide resolved
1. A keystore setting is created using this resource.
2. The keystore setting's value is modified to a different value using the Elasticsearch Service API.
3. Running `terraform apply` will fail to detect the changes and will not update the keystore setting to the value defined in the terraform configuration.
marclop marked this conversation as resolved.
Show resolved Hide resolved
To force the keystore setting to the value it is configured to hold, you may want to taint the resource and force its recreation.

Before you create Elasticsearch keystore settings, be sure to check out the [official Elasticsearch keystore documentation](https://www.elastic.co/guide/en/elasticsearch/reference/master/elasticsearch-keystore.html) and the [Elastic Cloud specific documentation](https://www.elastic.co/guide/en/cloud/current/ec-configuring-keystore.html).
marclop marked this conversation as resolved.
Show resolved Hide resolved

## Example Usage

These examples show how to use the resource at a basic level, so they work out the box and can be copied. The real utility of this resource will come when combined with other data providers, like vault or similar.
marclop marked this conversation as resolved.
Show resolved Hide resolved

### Adding a new keystore setting to your deployment

```hcl
data "ec_stack" "latest" {
version_regex = "latest"
region = "us-east-1"
}

# Create an Elastic Cloud deployment
resource "ec_deployment" "example_keystore" {
region = "us-east-1"
version = data.ec_stack.latest.version
deployment_template_id = "aws-io-optimized-v2"

elasticsearch {}
}

# Create the keystore secret entry
resource "ec_deployment_elasticsearch_keystore" "secure_url" {
deployment_id = ec_deployment.example_keystore.id
setting_name = "xpack.notification.slack.account.hello.secure_url"
value = "http://my-secure-url.com"
}

```

### Adding credentials to use GCS as a Snapshot repository
marclop marked this conversation as resolved.
Show resolved Hide resolved

For up to date documentation on the `setting_name`, refer to the [ESS documentation](https://www.elastic.co/guide/en/cloud/current/ec-gcs-snapshotting.html#ec-gcs-service-account-key).
marclop marked this conversation as resolved.
Show resolved Hide resolved

```hcl
data "ec_stack" "latest" {
version_regex = "latest"
region = "us-east-1"
}

# Create an Elastic Cloud deployment
resource "ec_deployment" "example_keystore" {
region = "us-east-1"
version = data.ec_stack.latest.version
deployment_template_id = "aws-io-optimized-v2"

elasticsearch {}
}

# Create the keystore secret entry
resource "ec_deployment_elasticsearch_keystore" "gcs_credential" {
deployment_id = ec_deployment.example_keystore.id
setting_name = "gcs.client.default.credentials_file"
value = file("service-account-key.json")
as_file = true
}
```

## Argument Reference
marclop marked this conversation as resolved.
Show resolved Hide resolved
The following arguments are supported:

* `deployment_id` - (Required) Deployment ID of the Deployment that holds the Elasticsearch cluster where the keystore setting will be written to.
marclop marked this conversation as resolved.
Show resolved Hide resolved
* `setting_name` - (Required) Required name for the keystore setting, if the setting already exists in the Elasticsearch cluster, it will be overridden.
* `value` - (Required) Value of this setting. This can either be a string or a JSON object that is stored as a JSON string in the keystore.
* `as_file` - (Optional) if set to `true`, stores the remote keystore setting as a file. The default is `false`, which stores the keystore setting as string when value is a plain string.
marclop marked this conversation as resolved.
Show resolved Hide resolved


## Attributes Reference
marclop marked this conversation as resolved.
Show resolved Hide resolved

There are no additional attributes exported by this resource other than the referenced arguments.

## Import

This resource cannot be imported.
53 changes: 53 additions & 0 deletions ec/acc/deployment_elasticsearch_kesytore_destroy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package acc

import (
"fmt"

"github.com/elastic/cloud-sdk-go/pkg/api/deploymentapi/eskeystoreapi"
"github.com/elastic/cloud-sdk-go/pkg/multierror"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func testAccDeploymentElasticsearchKeystoreDestroy(s *terraform.State) error {
// retrieve the connection established in Provider configuration
client, err := newAPI()
if err != nil {
return err
}

for _, rs := range s.RootModule().Resources {
if rs.Type != "ec_deployment_elasticsearch_keystore" {
continue
}

res, err := eskeystoreapi.Get(eskeystoreapi.GetParams{
API: client,
DeploymentID: rs.Primary.Attributes["deployment_id"],
})

if err == nil || res != nil {
return multierror.NewPrefixed("ec_deployment_elasticsearch_keystore found",
fmt.Errorf("deployment (%s) still exists", rs.Primary.Attributes["deployment_id"]),
)
}
}

return nil
}
143 changes: 143 additions & 0 deletions ec/acc/deployment_elasticsearch_kesytore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package acc

import (
"fmt"
"testing"

"github.com/elastic/cloud-sdk-go/pkg/multierror"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccDeploymentElasticsearchKeystore_full(t *testing.T) {
var previousID, currentID string

resType := "ec_deployment_elasticsearch_keystore"
firstResName := resType + ".test"
secondResName := resType + ".gcs_creds"
randomName := prefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
startCfg := "testdata/deployment_elasticsearch_keystore_1.tf"
updateKeystoreSetting := "testdata/deployment_elasticsearch_keystore_2.tf"
changeKeystoreSettingName := "testdata/deployment_elasticsearch_keystore_3.tf"
deleteAllKeystoreSettings := "testdata/deployment_elasticsearch_keystore_4.tf"

cfgF := func(cfg string) string {
return fixtureAccDeploymentResourceBasic(
t, cfg, randomName, getRegion(), defaultTemplate,
)
}

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactory,
CheckDestroy: resource.ComposeAggregateTestCheckFunc(
testAccDeploymentDestroy,
testAccDeploymentElasticsearchKeystoreDestroy,
),
Steps: []resource.TestStep{
{
Config: cfgF(startCfg),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(firstResName, "setting_name", "xpack.notification.slack.account.hello.secure_url"),
resource.TestCheckResourceAttr(firstResName, "value", "hella"),
resource.TestCheckResourceAttr(firstResName, "as_file", "false"),
resource.TestCheckResourceAttrSet(firstResName, "deployment_id"),

resource.TestCheckResourceAttr(secondResName, "setting_name", "gcs.client.secondary.credentials_file"),
resource.TestCheckResourceAttr(secondResName, "value", "{\n \"type\": \"service_account\",\n \"project_id\": \"project-id\",\n \"private_key_id\": \"key-id\",\n \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nprivate-key\\n-----END PRIVATE KEY-----\\n\",\n \"client_email\": \"service-account-email\",\n \"client_id\": \"client-id\",\n \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n \"token_uri\": \"https://accounts.google.com/o/oauth2/token\",\n \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/service-account-email\"\n}"),
resource.TestCheckResourceAttr(secondResName, "as_file", "false"),
resource.TestCheckResourceAttrSet(secondResName, "deployment_id"),
),
},
{
Config: cfgF(updateKeystoreSetting),
Check: resource.ComposeAggregateTestCheckFunc(
checkESKeystoreResourceID(firstResName, &previousID),

resource.TestCheckResourceAttr(firstResName, "setting_name", "xpack.notification.slack.account.hello.secure_url"),
resource.TestCheckResourceAttr(firstResName, "value", "hello2u"),
resource.TestCheckResourceAttr(firstResName, "as_file", "false"),
resource.TestCheckResourceAttrSet(firstResName, "deployment_id"),

resource.TestCheckResourceAttr(secondResName, "setting_name", "gcs.client.secondary.credentials_file"),
resource.TestCheckResourceAttr(secondResName, "value", "{\n \"type\": \"service_account\",\n \"project_id\": \"project-id\",\n \"private_key_id\": \"key-id\",\n \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nprivate-key\\n-----END PRIVATE KEY-----\\n\",\n \"client_email\": \"service-account-email\",\n \"client_id\": \"client-id\",\n \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n \"token_uri\": \"https://accounts.google.com/o/oauth2/token\",\n \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/service-account-email\"\n}"),
resource.TestCheckResourceAttr(secondResName, "as_file", "false"),
resource.TestCheckResourceAttrSet(secondResName, "deployment_id"),
),
},
{
Config: cfgF(changeKeystoreSettingName),
Check: resource.ComposeAggregateTestCheckFunc(
checkESKeystoreResourceID(firstResName, &currentID),

resource.TestCheckResourceAttr(firstResName, "setting_name", "xpack.notification.slack.account.hello.secure_urla"),
resource.TestCheckResourceAttr(firstResName, "value", "hello2u"),
resource.TestCheckResourceAttr(firstResName, "as_file", "false"),
resource.TestCheckResourceAttrSet(firstResName, "deployment_id"),

resource.TestCheckResourceAttr(secondResName, "setting_name", "gcs.client.secondary.credentials_file"),
resource.TestCheckResourceAttr(secondResName, "value", "{\n \"type\": \"service_account\",\n \"project_id\": \"project-id\",\n \"private_key_id\": \"key-id\",\n \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nprivate-key\\n-----END PRIVATE KEY-----\\n\",\n \"client_email\": \"service-account-email\",\n \"client_id\": \"client-id\",\n \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n \"token_uri\": \"https://accounts.google.com/o/oauth2/token\",\n \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/service-account-email\"\n}"),
resource.TestCheckResourceAttr(secondResName, "as_file", "false"),
resource.TestCheckResourceAttrSet(secondResName, "deployment_id"),
),
},
{
Config: cfgF(deleteAllKeystoreSettings),
Check: resource.ComposeAggregateTestCheckFunc(
checkNoKeystoreResourcesLeft(firstResName, secondResName),
func(current, previous *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *current == *previous {
return fmt.Errorf("%s id (%s) should not equal %s", firstResName, *current, *previous)
}
return nil
}
}(&currentID, &previousID),
),
},
},
})
}

func checkESKeystoreResourceID(resourceName string, id *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Not found: %s", resourceName)
}

*id = rs.Primary.ID
return nil
}
}

func checkNoKeystoreResourcesLeft(resourceName ...string) resource.TestCheckFunc {
return func(s *terraform.State) error {
merr := multierror.NewPrefixed("found 'ec_deployment_elasticsearch_keystore' resources")
for _, resName := range resourceName {
if rs, ok := s.RootModule().Resources[resName]; ok {
merr = merr.Append(fmt.Errorf("found: %s with ID: %s", resName, rs.Primary.ID))
}
}

return merr.ErrorOrNil()
}
}
32 changes: 32 additions & 0 deletions ec/acc/testdata/deployment_elasticsearch_keystore_1.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
data "ec_stack" "keystore" {
version_regex = "latest"
region = "%s"
}

resource "ec_deployment" "keystore" {
name = "%s"
region = "%s"
version = data.ec_stack.keystore.version
deployment_template_id = "%s"

elasticsearch {
topology {
id = "hot_content"
size = "1g"
zone_count = 1
}
}
}

resource "ec_deployment_elasticsearch_keystore" "test" {
deployment_id = ec_deployment.keystore.id
setting_name = "xpack.notification.slack.account.hello.secure_url"
value = "hella"
}

resource "ec_deployment_elasticsearch_keystore" "gcs_creds" {
deployment_id = ec_deployment.keystore.id
setting_name = "gcs.client.secondary.credentials_file"
value = file("testdata/deployment_elasticsearch_keystore_creds.json")
}

31 changes: 31 additions & 0 deletions ec/acc/testdata/deployment_elasticsearch_keystore_2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
data "ec_stack" "keystore" {
version_regex = "latest"
region = "%s"
}

resource "ec_deployment" "keystore" {
name = "%s"
region = "%s"
version = data.ec_stack.keystore.version
deployment_template_id = "%s"

elasticsearch {
topology {
id = "hot_content"
size = "1g"
zone_count = 1
}
}
}

resource "ec_deployment_elasticsearch_keystore" "test" {
deployment_id = ec_deployment.keystore.id
setting_name = "xpack.notification.slack.account.hello.secure_url"
value = "hello2u"
}

resource "ec_deployment_elasticsearch_keystore" "gcs_creds" {
deployment_id = ec_deployment.keystore.id
setting_name = "gcs.client.secondary.credentials_file"
value = file("testdata/deployment_elasticsearch_keystore_creds.json")
}
31 changes: 31 additions & 0 deletions ec/acc/testdata/deployment_elasticsearch_keystore_3.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
data "ec_stack" "keystore" {
version_regex = "latest"
region = "%s"
}

resource "ec_deployment" "keystore" {
name = "%s"
region = "%s"
version = data.ec_stack.keystore.version
deployment_template_id = "%s"

elasticsearch {
topology {
id = "hot_content"
size = "1g"
zone_count = 1
}
}
}

resource "ec_deployment_elasticsearch_keystore" "test" {
deployment_id = ec_deployment.keystore.id
setting_name = "xpack.notification.slack.account.hello.secure_urla"
value = "hello2u"
}

resource "ec_deployment_elasticsearch_keystore" "gcs_creds" {
deployment_id = ec_deployment.keystore.id
setting_name = "gcs.client.secondary.credentials_file"
value = file("testdata/deployment_elasticsearch_keystore_creds.json")
}
Loading