Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 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
}
68 changes: 68 additions & 0 deletions internal/clients/kibana/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,74 @@ func GetConnector(ctx context.Context, apiClient *clients.ApiClient, connectorID
return connector, nil
}

func SearchConnector(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) == 1 {
return foundConnectors[0], nil
}

if len(foundConnectors) > 1 {
return nil, diag.Errorf("error while creating elasticstack_kibana_action_connector datasource: multiple connectors with name [%s/%s] found", spaceID, connectorName)
}

return nil, diag.Errorf("error while creating elasticstack_kibana_action_connector datasource: connector [%s/%s] not found", spaceID, connectorName)
Comment thread
wandergeek marked this conversation as resolved.
Outdated
}

func DeleteConnector(ctx context.Context, apiClient *clients.ApiClient, connectorID string, spaceID string) diag.Diagnostics {
client, err := apiClient.GetKibanaConnectorsClient(ctx)
if err != nil {
Expand Down
106 changes: 106 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,104 @@ 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
}
]`

var requests []*http.Request
var mockResponses []string
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(http.StatusOK)
_, 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()

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 := SearchConnector(context.Background(), apiClient, "my-connector", "default", "")
require.Nil(t, diags)
require.NotNil(t, connector)

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

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

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

mockResponses = append(mockResponses, getConnectorsResponse)
successConnector, diags := SearchConnector(context.Background(), apiClient, "my-connecctor", "default", ".index")
require.NotNil(t, diags)
require.Nil(t, successConnector)

}
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
82 changes: 82 additions & 0 deletions internal/kibana/connector_data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package kibana

import (
"context"

"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func DataSourceConnector() *schema.Resource {
Comment thread
wandergeek marked this conversation as resolved.
var connectorSchema = map[string]*schema.Schema{
"connector_id": {
Description: "A UUID v1 or v4 randomly generated ID.",
Type: schema.TypeString,
Computed: true,
},
"space_id": {
Description: "An identifier for the space. If space_id is not provided, the default space is used.",
Type: schema.TypeString,
Optional: true,
Default: "default",
},
"name": {
Description: "The name of the connector. While this name does not have to be unique, a distinctive name can help you identify a connector.",
Type: schema.TypeString,
Required: true,
},
"connector_type_id": {
Description: "The ID of the connector type, e.g. `.index`.",
Type: schema.TypeString,
Optional: true,
Comment thread
wandergeek marked this conversation as resolved.
},
"config": {
Description: "The configuration for the connector. Configuration properties vary depending on the connector type.",
Type: schema.TypeString,
Computed: true,
},
"is_deprecated": {
Description: "Indicates whether the connector type is deprecated.",
Type: schema.TypeBool,
Computed: true,
},
"is_missing_secrets": {
Description: "Indicates whether secrets are missing for the connector.",
Type: schema.TypeBool,
Computed: true,
},
"is_preconfigured": {
Description: "Indicates whether it is a preconfigured connector.",
Type: schema.TypeBool,
Computed: true,
},
}

return &schema.Resource{
Description: "Search for a connector by name, space id, and type. Note, that this data source will fail if more than one connector shares the same name.",
ReadContext: datasourceConnectorRead,
Schema: connectorSchema,
}
}

func datasourceConnectorRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}
name := d.Get("name").(string)
spaceId := d.Get("space_id").(string)
connectorType := d.Get("connector_type_id").(string)

connector, diags := kibana.SearchConnector(ctx, client, name, spaceId, connectorType)
if diags.HasError() {
return diags
}
Comment thread
wandergeek marked this conversation as resolved.

compositeID := &clients.CompositeId{ClusterId: spaceId, ResourceId: connector.ConnectorID}
d.SetId(compositeID.String())

return flattenActionConnector(connector, d)
}
Loading