Skip to content
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
- Prevent a provider panic when an `elasticstack_elasticsearch_template` or `elasticstack_elasticsearch_component_template` includes an empty `template` (`template {}`) block. ([#598](https://github.com/elastic/terraform-provider-elasticstack/pull/598))
- Prevent `elasticstack_kibana_space` to attempt the space recreation if `initials` and `color` are not provided. ([#606](https://github.com/elastic/terraform-provider-elasticstack/pull/606))

### Added

- Added datasource for alerting connectors. ([#607](https://github.com/elastic/terraform-provider-elasticstack/pull/607))

## [0.11.2] - 2024-03-13

### Fixed
Expand Down
51 changes: 51 additions & 0 deletions docs/data-sources/kibana_action_connector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
subcategory: "Kibana"
layout: ""
page_title: "Elasticstack: elasticstack_kibana_action_connector Data Source"
description: |-
Retrieve a specific action connector role. See https://www.elastic.co/guide/en/kibana/current/get-all-connectors-api.html.
---

# Data Source: elasticstack_kibana_action_connector

Use this data source to get information about an existing action connector.

## Example Usage

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

data "elasticstack_kibana_action_connector" "example" {
name = "myslackconnector"
space_id = "default"
connector_type_id = ".slack"
}

output "connector_id" {
value = data.elasticstack_kibana_action_connector.example.connector_id
}
```

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

### Required

- `name` (String) The name of the connector. While this name does not have to be unique, a distinctive name can help you identify a connector.

### Optional

- `connector_type_id` (String) The ID of the connector type, e.g. `.index`.
- `space_id` (String) An identifier for the space. If space_id is not provided, the default space is used.

### Read-Only

- `config` (String) The configuration for the connector. Configuration properties vary depending on the connector type.
- `connector_id` (String) A UUID v1 or v4 randomly generated ID.
- `id` (String) The ID of this resource.
- `is_deprecated` (Boolean) Indicates whether the connector type is deprecated.
- `is_missing_secrets` (Boolean) Indicates whether secrets are missing for the connector.
- `is_preconfigured` (Boolean) Indicates whether it is a preconfigured connector.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
provider "elasticstack" {
elasticsearch {}
kibana {}
}

data "elasticstack_kibana_action_connector" "example" {
name = "myslackconnector"
space_id = "default"
connector_type_id = ".slack"
}

output "connector_id" {
value = data.elasticstack_kibana_action_connector.example.connector_id
}
64 changes: 64 additions & 0 deletions internal/clients/kibana/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/models"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
)

Expand Down Expand Up @@ -141,6 +142,69 @@ func GetConnector(ctx context.Context, apiClient *clients.ApiClient, connectorID
return connector, nil
}

func SearchConnectors(ctx context.Context, apiClient *clients.ApiClient, connectorName, spaceID, connectorTypeID string) ([]*models.KibanaActionConnector, diag.Diagnostics) {
client, err := apiClient.GetKibanaConnectorsClient(ctx)
if err != nil {
return nil, diag.FromErr(err)
}

httpResp, err := client.GetConnectors(ctx, spaceID)

if err != nil {
return nil, diag.Errorf("unable to get connectors: [%v]", err)
}

defer httpResp.Body.Close()

resp, err := connectors.ParseGetConnectorsResponse(httpResp)
if err != nil {
return nil, diag.Errorf("unable to parse connectors get response: [%v]", err)
}

if resp.JSON401 != nil {
return nil, diag.Errorf("%s: %s", *resp.JSON401.Error, *resp.JSON401.Message)
}

if resp.JSON200 == nil {
return nil, diag.Errorf("%s: %s", resp.Status(), string(resp.Body))
}

foundConnectors := []*models.KibanaActionConnector{}
for _, connector := range *resp.JSON200 {
if connector.Name != connectorName {
continue
}

if connectorTypeID != "" && string(connector.ConnectorTypeId) != connectorTypeID {
continue
}

//this marshaling and unmarshaling business allows us to create a type with unexported fields.
bytes, err := json.Marshal(connector)
if err != nil {
return nil, diag.Errorf("cannot marshal connector: %v", err)
}

var respProps connectors.ConnectorResponseProperties
err = json.Unmarshal(bytes, &respProps)
if err != nil {
return nil, diag.Errorf("cannot unmarshal connector: %v", err)
}

c, err := connectorResponseToModel(spaceID, respProps)
if err != nil {
return nil, diag.Errorf("unable to convert response to model: %v", err)
}

foundConnectors = append(foundConnectors, c)
}
if len(foundConnectors) == 0 {
tflog.Debug(ctx, fmt.Sprintf("no connectors found with name [%s/%s] and type [%s]", spaceID, connectorName, connectorTypeID))
}

return foundConnectors, nil
}

func DeleteConnector(ctx context.Context, apiClient *clients.ApiClient, connectorID string, spaceID string) diag.Diagnostics {
client, err := apiClient.GetKibanaConnectorsClient(ctx)
if err != nil {
Expand Down
121 changes: 121 additions & 0 deletions internal/clients/kibana/connector_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package kibana

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"

"github.com/elastic/terraform-provider-elasticstack/generated/connectors"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/models"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -130,3 +135,119 @@ func Test_connectorResponseToModel(t *testing.T) {
})
}
}

func TestGetConnectorByName(t *testing.T) {
const getConnectorsResponse = `[
{
"id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad",
"connector_type_id": ".index",
"name": "my-connector",
"config": {
"index": "test-index",
"refresh": false,
"executionTimeField": null
},
"is_preconfigured": false,
"is_deprecated": false,
"is_missing_secrets": false,
"referenced_by_count": 3
},
{
"id": "d55b6eb0-6bad-11eb-9f3b-611eebc6c3ad",
"connector_type_id": ".index",
"name": "doubledup-connector",
"config": {
"index": "test-index",
"refresh": false,
"executionTimeField": null
},
"is_preconfigured": false,
"is_deprecated": false,
"is_missing_secrets": false,
"referenced_by_count": 3
},
{
"id": "855b6eb0-6bad-11eb-9f3b-611eebc6c3ad",
"connector_type_id": ".index",
"name": "doubledup-connector",
"config": {
"index": "test-index",
"refresh": false,
"executionTimeField": null
},
"is_preconfigured": false,
"is_deprecated": false,
"is_missing_secrets": false,
"referenced_by_count": 0
}
]`

const emptyConnectorsResponse = `[]`

var requests []*http.Request
var mockResponses []string
var httpStatus int
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
requests = append(requests, req)

if len(mockResponses) > 0 {
r := []byte(mockResponses[0])
rw.Header().Add("X-Elastic-Product", "Elasticsearch")
rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(httpStatus)
_, err := rw.Write(r)
require.NoError(t, err)
mockResponses = mockResponses[1:]
} else {
t.Fatalf("Unexpected request: %s %s", req.Method, req.URL.Path)
}
}))
defer server.Close()

httpStatus = http.StatusOK
mockResponses = append(mockResponses, getConnectorsResponse)

err := os.Setenv("ELASTICSEARCH_URL", server.URL)
require.NoError(t, err)
err = os.Setenv("KIBANA_ENDPOINT", server.URL)
require.NoError(t, err)

apiClient, err := clients.NewAcceptanceTestingClient()
require.NoError(t, err)

connector, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", "")
require.Nil(t, diags)
require.NotNil(t, connector)

mockResponses = append(mockResponses, getConnectorsResponse)
failConnector, diags := SearchConnectors(context.Background(), apiClient, "failwhale", "default", "")
require.Nil(t, diags)
require.Empty(t, failConnector)

mockResponses = append(mockResponses, getConnectorsResponse)
dupConnector, diags := SearchConnectors(context.Background(), apiClient, "doubledup-connector", "default", "")
require.Nil(t, diags)
require.Len(t, dupConnector, 2)

mockResponses = append(mockResponses, getConnectorsResponse)
wrongConnectorType, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", ".slack")
require.Nil(t, diags)
require.Empty(t, wrongConnectorType)

mockResponses = append(mockResponses, getConnectorsResponse)
successConnector, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", ".index")
require.Nil(t, diags)
require.Len(t, successConnector, 1)

mockResponses = append(mockResponses, emptyConnectorsResponse)
emptyConnector, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", "")
require.Nil(t, diags)
require.Empty(t, emptyConnector)

httpStatus = http.StatusBadGateway
mockResponses = append(mockResponses, emptyConnectorsResponse)
fail, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", "")
require.NotNil(t, diags)
require.Nil(t, fail)

}
5 changes: 2 additions & 3 deletions internal/kibana/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

func ResourceActionConnector() *schema.Resource {
apikeySchema := map[string]*schema.Schema{
var connectorSchema = map[string]*schema.Schema{
"connector_id": {
Description: "A UUID v1 or v4 to use instead of a randomly generated ID.",
Type: schema.TypeString,
Expand Down Expand Up @@ -69,7 +69,6 @@ func ResourceActionConnector() *schema.Resource {
Computed: true,
},
}

return &schema.Resource{
Description: "Creates a Kibana action connector. See https://www.elastic.co/guide/en/kibana/current/action-types.html",

Expand All @@ -83,7 +82,7 @@ func ResourceActionConnector() *schema.Resource {
StateContext: schema.ImportStatePassthroughContext,
},

Schema: apikeySchema,
Schema: connectorSchema,
}
}

Expand Down
Loading