Skip to content

Commit f75ff5a

Browse files
authored
Fix state upgrade for connectors with empty config (#1355)
* Fix state upgrade for connectors with empty config * Changelog
1 parent c86b803 commit f75ff5a

File tree

6 files changed

+607
-2
lines changed

6 files changed

+607
-2
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## [Unreleased]
22

3+
- Fix provider crash with `elasticstack_kibana_action_connector` when `config` or `secrets` was unset in 0.11.17 ([#1355](https://github.com/elastic/terraform-provider-elasticstack/pull/1355))
4+
5+
## [0.11.18] - 2025-10-10
6+
37
- Create `elasticstack_kibana_security_detection_rule` resource. ([#1290](https://github.com/elastic/terraform-provider-elasticstack/pull/1290))
48
- Add `elasticstack_kibana_export_saved_objects` data source ([#1293](https://github.com/elastic/terraform-provider-elasticstack/pull/1293))
59
- Create `elasticstack_kibana_maintenance_window` resource. ([#1224](https://github.com/elastic/terraform-provider-elasticstack/pull/1224))
@@ -461,7 +465,8 @@
461465
- Initial set of docs
462466
- CI integration
463467
464-
[Unreleased]: https://github.com/elastic/terraform-provider-elasticstack/compare/v0.11.17...HEAD
468+
[Unreleased]: https://github.com/elastic/terraform-provider-elasticstack/compare/v0.11.18...HEAD
469+
[0.11.18]: https://github.com/elastic/terraform-provider-elasticstack/compare/v0.11.17...v0.11.18
465470
[0.11.17]: https://github.com/elastic/terraform-provider-elasticstack/compare/v0.11.16...v0.11.17
466471
[0.11.16]: https://github.com/elastic/terraform-provider-elasticstack/compare/v0.11.15...v0.11.16
467472
[0.11.15]: https://github.com/elastic/terraform-provider-elasticstack/compare/v0.11.14...v0.11.15

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.DEFAULT_GOAL = help
22
SHELL := /bin/bash
33

4-
VERSION ?= 0.11.17
4+
VERSION ?= 0.11.18
55

66
NAME = elasticstack
77
BINARY = terraform-provider-${NAME}

internal/kibana/connectors/acc_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,59 @@ func TestAccResourceKibanaConnectorFromSDK(t *testing.T) {
329329
},
330330
})
331331
}
332+
333+
func TestAccResourceKibanaConnectorEmptyConfigFromSDK(t *testing.T) {
334+
minSupportedVersion := version.Must(version.NewSemver("7.14.0"))
335+
336+
connectorName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
337+
338+
create := func(name string) string {
339+
return fmt.Sprintf(`
340+
provider "elasticstack" {
341+
elasticsearch {}
342+
kibana {}
343+
}
344+
345+
resource "elasticstack_kibana_action_connector" "test" {
346+
name = "%s"
347+
connector_type_id = ".slack"
348+
secrets = jsonencode({
349+
webhookUrl = "https://example.com/webhook"
350+
})
351+
}`,
352+
name)
353+
}
354+
355+
resource.Test(t, resource.TestCase{
356+
PreCheck: func() { acctest.PreCheck(t) },
357+
CheckDestroy: checkResourceKibanaConnectorDestroy,
358+
Steps: []resource.TestStep{
359+
{
360+
// Create the connector with the last provider version where the connector resource was built on the SDK
361+
ExternalProviders: map[string]resource.ExternalProvider{
362+
"elasticstack": {
363+
Source: "elastic/elasticstack",
364+
VersionConstraint: "0.11.17",
365+
},
366+
},
367+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedVersion),
368+
Config: create(connectorName),
369+
Check: resource.ComposeTestCheckFunc(
370+
testCommonAttributes(connectorName, ".slack"),
371+
372+
resource.TestCheckResourceAttr("elasticstack_kibana_action_connector.test", "config", ""),
373+
),
374+
},
375+
{
376+
ProtoV6ProviderFactories: acctest.Providers,
377+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedVersion),
378+
Config: create(connectorName),
379+
Check: resource.ComposeTestCheckFunc(
380+
testCommonAttributes(connectorName, ".slack"),
381+
382+
resource.TestCheckResourceAttr("elasticstack_kibana_action_connector.test", "config", `{"__tf_provider_connector_type_id":".slack"}`),
383+
),
384+
},
385+
},
386+
})
387+
}

internal/kibana/connectors/resource.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package connectors
22

33
import (
44
"context"
5+
"encoding/json"
56

67
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
78
"github.com/hashicorp/go-version"
89
"github.com/hashicorp/terraform-plugin-framework/path"
910
"github.com/hashicorp/terraform-plugin-framework/resource"
11+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
1012
)
1113

1214
// Ensure provider defined types fully satisfy framework interfaces
@@ -35,3 +37,50 @@ func (r *Resource) Metadata(ctx context.Context, request resource.MetadataReques
3537
func (r *Resource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
3638
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("id"), request.ID)...)
3739
}
40+
41+
func (r *Resource) UpgradeState(context.Context) map[int64]resource.StateUpgrader {
42+
return map[int64]resource.StateUpgrader{
43+
0: {StateUpgrader: upgradeV0},
44+
}
45+
}
46+
47+
// The schema between V0 and V1 is mostly the same, however config saved ""
48+
// values to the state when null values were in the config. jsontypes.Normalized
49+
// correctly states this is invalid JSON.
50+
func upgradeV0(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
51+
var state map[string]interface{}
52+
53+
removeEmptyString := func(state map[string]interface{}, key string) map[string]interface{} {
54+
value, ok := state[key]
55+
if !ok {
56+
return state
57+
}
58+
59+
valueString, ok := value.(string)
60+
if !ok || valueString != "" {
61+
return state
62+
}
63+
64+
delete(state, key)
65+
return state
66+
}
67+
68+
err := json.Unmarshal(req.RawState.JSON, &state)
69+
if err != nil {
70+
resp.Diagnostics.AddError("Failed to unmarshal state", err.Error())
71+
return
72+
}
73+
74+
state = removeEmptyString(state, "config")
75+
state = removeEmptyString(state, "secrets")
76+
77+
stateBytes, err := json.Marshal(state)
78+
if err != nil {
79+
resp.Diagnostics.AddError("Failed to marshal state", err.Error())
80+
return
81+
}
82+
83+
resp.DynamicValue = &tfprotov6.DynamicValue{
84+
JSON: stateBytes,
85+
}
86+
}

0 commit comments

Comments
 (0)