Skip to content

Commit 5e8af6d

Browse files
authored
Add alerting connector data source (#607)
1 parent f3a9152 commit 5e8af6d

File tree

10 files changed

+410
-4
lines changed

10 files changed

+410
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
- 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))
66
- 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))
77

8+
### Added
9+
10+
- Added datasource for alerting connectors. ([#607](https://github.com/elastic/terraform-provider-elasticstack/pull/607))
11+
812
## [0.11.2] - 2024-03-13
913

1014
### Fixed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
subcategory: "Kibana"
3+
layout: ""
4+
page_title: "Elasticstack: elasticstack_kibana_action_connector Data Source"
5+
description: |-
6+
Retrieve a specific action connector role. See https://www.elastic.co/guide/en/kibana/current/get-all-connectors-api.html.
7+
---
8+
9+
# Data Source: elasticstack_kibana_action_connector
10+
11+
Use this data source to get information about an existing action connector.
12+
13+
## Example Usage
14+
15+
```terraform
16+
provider "elasticstack" {
17+
elasticsearch {}
18+
kibana {}
19+
}
20+
21+
data "elasticstack_kibana_action_connector" "example" {
22+
name = "myslackconnector"
23+
space_id = "default"
24+
connector_type_id = ".slack"
25+
}
26+
27+
output "connector_id" {
28+
value = data.elasticstack_kibana_action_connector.example.connector_id
29+
}
30+
```
31+
32+
<!-- schema generated by tfplugindocs -->
33+
## Schema
34+
35+
### Required
36+
37+
- `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.
38+
39+
### Optional
40+
41+
- `connector_type_id` (String) The ID of the connector type, e.g. `.index`.
42+
- `space_id` (String) An identifier for the space. If space_id is not provided, the default space is used.
43+
44+
### Read-Only
45+
46+
- `config` (String) The configuration for the connector. Configuration properties vary depending on the connector type.
47+
- `connector_id` (String) A UUID v1 or v4 randomly generated ID.
48+
- `id` (String) The ID of this resource.
49+
- `is_deprecated` (Boolean) Indicates whether the connector type is deprecated.
50+
- `is_missing_secrets` (Boolean) Indicates whether secrets are missing for the connector.
51+
- `is_preconfigured` (Boolean) Indicates whether it is a preconfigured connector.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
provider "elasticstack" {
2+
elasticsearch {}
3+
kibana {}
4+
}
5+
6+
data "elasticstack_kibana_action_connector" "example" {
7+
name = "myslackconnector"
8+
space_id = "default"
9+
connector_type_id = ".slack"
10+
}
11+
12+
output "connector_id" {
13+
value = data.elasticstack_kibana_action_connector.example.connector_id
14+
}

internal/clients/kibana/connector.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
1212
"github.com/elastic/terraform-provider-elasticstack/internal/models"
1313
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
14+
"github.com/hashicorp/terraform-plugin-log/tflog"
1415
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1516
)
1617

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

145+
func SearchConnectors(ctx context.Context, apiClient *clients.ApiClient, connectorName, spaceID, connectorTypeID string) ([]*models.KibanaActionConnector, diag.Diagnostics) {
146+
client, err := apiClient.GetKibanaConnectorsClient(ctx)
147+
if err != nil {
148+
return nil, diag.FromErr(err)
149+
}
150+
151+
httpResp, err := client.GetConnectors(ctx, spaceID)
152+
153+
if err != nil {
154+
return nil, diag.Errorf("unable to get connectors: [%v]", err)
155+
}
156+
157+
defer httpResp.Body.Close()
158+
159+
resp, err := connectors.ParseGetConnectorsResponse(httpResp)
160+
if err != nil {
161+
return nil, diag.Errorf("unable to parse connectors get response: [%v]", err)
162+
}
163+
164+
if resp.JSON401 != nil {
165+
return nil, diag.Errorf("%s: %s", *resp.JSON401.Error, *resp.JSON401.Message)
166+
}
167+
168+
if resp.JSON200 == nil {
169+
return nil, diag.Errorf("%s: %s", resp.Status(), string(resp.Body))
170+
}
171+
172+
foundConnectors := []*models.KibanaActionConnector{}
173+
for _, connector := range *resp.JSON200 {
174+
if connector.Name != connectorName {
175+
continue
176+
}
177+
178+
if connectorTypeID != "" && string(connector.ConnectorTypeId) != connectorTypeID {
179+
continue
180+
}
181+
182+
//this marshaling and unmarshaling business allows us to create a type with unexported fields.
183+
bytes, err := json.Marshal(connector)
184+
if err != nil {
185+
return nil, diag.Errorf("cannot marshal connector: %v", err)
186+
}
187+
188+
var respProps connectors.ConnectorResponseProperties
189+
err = json.Unmarshal(bytes, &respProps)
190+
if err != nil {
191+
return nil, diag.Errorf("cannot unmarshal connector: %v", err)
192+
}
193+
194+
c, err := connectorResponseToModel(spaceID, respProps)
195+
if err != nil {
196+
return nil, diag.Errorf("unable to convert response to model: %v", err)
197+
}
198+
199+
foundConnectors = append(foundConnectors, c)
200+
}
201+
if len(foundConnectors) == 0 {
202+
tflog.Debug(ctx, fmt.Sprintf("no connectors found with name [%s/%s] and type [%s]", spaceID, connectorName, connectorTypeID))
203+
}
204+
205+
return foundConnectors, nil
206+
}
207+
144208
func DeleteConnector(ctx context.Context, apiClient *clients.ApiClient, connectorID string, spaceID string) diag.Diagnostics {
145209
client, err := apiClient.GetKibanaConnectorsClient(ctx)
146210
if err != nil {

internal/clients/kibana/connector_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package kibana
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
7+
"net/http"
8+
"net/http/httptest"
9+
"os"
610
"testing"
711

812
"github.com/elastic/terraform-provider-elasticstack/generated/connectors"
13+
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
914
"github.com/elastic/terraform-provider-elasticstack/internal/models"
1015
"github.com/stretchr/testify/require"
1116
)
@@ -130,3 +135,119 @@ func Test_connectorResponseToModel(t *testing.T) {
130135
})
131136
}
132137
}
138+
139+
func TestGetConnectorByName(t *testing.T) {
140+
const getConnectorsResponse = `[
141+
{
142+
"id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad",
143+
"connector_type_id": ".index",
144+
"name": "my-connector",
145+
"config": {
146+
"index": "test-index",
147+
"refresh": false,
148+
"executionTimeField": null
149+
},
150+
"is_preconfigured": false,
151+
"is_deprecated": false,
152+
"is_missing_secrets": false,
153+
"referenced_by_count": 3
154+
},
155+
{
156+
"id": "d55b6eb0-6bad-11eb-9f3b-611eebc6c3ad",
157+
"connector_type_id": ".index",
158+
"name": "doubledup-connector",
159+
"config": {
160+
"index": "test-index",
161+
"refresh": false,
162+
"executionTimeField": null
163+
},
164+
"is_preconfigured": false,
165+
"is_deprecated": false,
166+
"is_missing_secrets": false,
167+
"referenced_by_count": 3
168+
},
169+
{
170+
"id": "855b6eb0-6bad-11eb-9f3b-611eebc6c3ad",
171+
"connector_type_id": ".index",
172+
"name": "doubledup-connector",
173+
"config": {
174+
"index": "test-index",
175+
"refresh": false,
176+
"executionTimeField": null
177+
},
178+
"is_preconfigured": false,
179+
"is_deprecated": false,
180+
"is_missing_secrets": false,
181+
"referenced_by_count": 0
182+
}
183+
]`
184+
185+
const emptyConnectorsResponse = `[]`
186+
187+
var requests []*http.Request
188+
var mockResponses []string
189+
var httpStatus int
190+
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
191+
requests = append(requests, req)
192+
193+
if len(mockResponses) > 0 {
194+
r := []byte(mockResponses[0])
195+
rw.Header().Add("X-Elastic-Product", "Elasticsearch")
196+
rw.Header().Add("Content-Type", "application/json")
197+
rw.WriteHeader(httpStatus)
198+
_, err := rw.Write(r)
199+
require.NoError(t, err)
200+
mockResponses = mockResponses[1:]
201+
} else {
202+
t.Fatalf("Unexpected request: %s %s", req.Method, req.URL.Path)
203+
}
204+
}))
205+
defer server.Close()
206+
207+
httpStatus = http.StatusOK
208+
mockResponses = append(mockResponses, getConnectorsResponse)
209+
210+
err := os.Setenv("ELASTICSEARCH_URL", server.URL)
211+
require.NoError(t, err)
212+
err = os.Setenv("KIBANA_ENDPOINT", server.URL)
213+
require.NoError(t, err)
214+
215+
apiClient, err := clients.NewAcceptanceTestingClient()
216+
require.NoError(t, err)
217+
218+
connector, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", "")
219+
require.Nil(t, diags)
220+
require.NotNil(t, connector)
221+
222+
mockResponses = append(mockResponses, getConnectorsResponse)
223+
failConnector, diags := SearchConnectors(context.Background(), apiClient, "failwhale", "default", "")
224+
require.Nil(t, diags)
225+
require.Empty(t, failConnector)
226+
227+
mockResponses = append(mockResponses, getConnectorsResponse)
228+
dupConnector, diags := SearchConnectors(context.Background(), apiClient, "doubledup-connector", "default", "")
229+
require.Nil(t, diags)
230+
require.Len(t, dupConnector, 2)
231+
232+
mockResponses = append(mockResponses, getConnectorsResponse)
233+
wrongConnectorType, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", ".slack")
234+
require.Nil(t, diags)
235+
require.Empty(t, wrongConnectorType)
236+
237+
mockResponses = append(mockResponses, getConnectorsResponse)
238+
successConnector, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", ".index")
239+
require.Nil(t, diags)
240+
require.Len(t, successConnector, 1)
241+
242+
mockResponses = append(mockResponses, emptyConnectorsResponse)
243+
emptyConnector, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", "")
244+
require.Nil(t, diags)
245+
require.Empty(t, emptyConnector)
246+
247+
httpStatus = http.StatusBadGateway
248+
mockResponses = append(mockResponses, emptyConnectorsResponse)
249+
fail, diags := SearchConnectors(context.Background(), apiClient, "my-connector", "default", "")
250+
require.NotNil(t, diags)
251+
require.Nil(t, fail)
252+
253+
}

internal/kibana/connector.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
func ResourceActionConnector() *schema.Resource {
16-
apikeySchema := map[string]*schema.Schema{
16+
var connectorSchema = map[string]*schema.Schema{
1717
"connector_id": {
1818
Description: "A UUID v1 or v4 to use instead of a randomly generated ID.",
1919
Type: schema.TypeString,
@@ -69,7 +69,6 @@ func ResourceActionConnector() *schema.Resource {
6969
Computed: true,
7070
},
7171
}
72-
7372
return &schema.Resource{
7473
Description: "Creates a Kibana action connector. See https://www.elastic.co/guide/en/kibana/current/action-types.html",
7574

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

86-
Schema: apikeySchema,
85+
Schema: connectorSchema,
8786
}
8887
}
8988

0 commit comments

Comments
 (0)