Skip to content

Commit 08dd5f0

Browse files
helper/resource: Add new StateStore test mode for smoke testing Terraform state storage (#591)
* initial implementation of declarative state store SDK + provider * add workspace commands to wd * initial impl + working state store impl * refactoring state store mode and add new tests * refactoring * remove comment * fix up some comments * comments * changelog * add copywrite headers (manually :P) * linting errors fixed * add skips for pre 1.4 * use main for tf-exec * remove toolchain * tf-exec wrong dep * use new plugin-go changes * update plugin go * build(deps): Bump actions/setup-go in the github-actions group (#594) Bumps the github-actions group with 1 update: [actions/setup-go](https://github.com/actions/setup-go). Updates `actions/setup-go` from 6.1.0 to 6.2.0 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](actions/setup-go@4dc6199...7a3fe6c) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * update to use main branch commits --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 40414ec commit 08dd5f0

File tree

18 files changed

+1448
-30
lines changed

18 files changed

+1448
-30
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: FEATURES
2+
body: 'helper/resource: Added new `StateStore` testing mode to `TestStep`, which can be used to smoke test Terraform state storage.'
3+
time: 2026-01-09T17:09:55.56054-05:00
4+
custom:
5+
Issue: "591"

go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ require (
1111
github.com/hashicorp/hc-install v0.9.2
1212
github.com/hashicorp/hcl/v2 v2.24.0
1313
github.com/hashicorp/logutils v1.0.0
14-
github.com/hashicorp/terraform-exec v0.24.0
14+
github.com/hashicorp/terraform-exec v0.24.1-0.20260121172827-e91e9600ec9b
1515
github.com/hashicorp/terraform-json v0.27.2
16-
github.com/hashicorp/terraform-plugin-go v0.29.0
16+
github.com/hashicorp/terraform-plugin-go v0.29.1-0.20260122204301-b0a6aca80fd7
1717
github.com/hashicorp/terraform-plugin-log v0.10.0
1818
github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1
1919
github.com/mitchellh/go-testing-interface v1.14.1
@@ -54,7 +54,7 @@ require (
5454
golang.org/x/text v0.33.0 // indirect
5555
golang.org/x/tools v0.40.0 // indirect
5656
google.golang.org/appengine v1.6.8 // indirect
57-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
58-
google.golang.org/grpc v1.75.1 // indirect
59-
google.golang.org/protobuf v1.36.9 // indirect
57+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
58+
google.golang.org/grpc v1.78.0 // indirect
59+
google.golang.org/protobuf v1.36.11 // indirect
6060
)

go.sum

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQx
7777
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
7878
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
7979
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
80-
github.com/hashicorp/terraform-exec v0.24.0 h1:mL0xlk9H5g2bn0pPF6JQZk5YlByqSqrO5VoaNtAf8OE=
81-
github.com/hashicorp/terraform-exec v0.24.0/go.mod h1:lluc/rDYfAhYdslLJQg3J0oDqo88oGQAdHR+wDqFvo4=
80+
github.com/hashicorp/terraform-exec v0.24.1-0.20260121172827-e91e9600ec9b h1:fJUuEi2V7XraZP/KFvfEsfG+YoszjVp1jz1wRH2g3c0=
81+
github.com/hashicorp/terraform-exec v0.24.1-0.20260121172827-e91e9600ec9b/go.mod h1:/+qarFaJUhdMK58b2hpaEEypGqsGPfauUuSOiengLr4=
8282
github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU=
8383
github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
84-
github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU=
85-
github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM=
84+
github.com/hashicorp/terraform-plugin-go v0.29.1-0.20260122204301-b0a6aca80fd7 h1:ViO8kjIwBFTqOrMT5GilsY8TKRjejAqSTyFjjdTMY1o=
85+
github.com/hashicorp/terraform-plugin-go v0.29.1-0.20260122204301-b0a6aca80fd7/go.mod h1:q0rLI/Z8QaAhX+22MvIlGbFkHoOtwS58vaShJZPXndU=
8686
github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g=
8787
github.com/hashicorp/terraform-plugin-log v0.10.0/go.mod h1:/9RR5Cv2aAbrqcTSdNmY1NRHP4E3ekrXRGjqORpXyB0=
8888
github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 h1:mlAq/OrMlg04IuJT7NpefI1wwtdpWudnEmjuQs04t/4=
@@ -155,18 +155,18 @@ github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0
155155
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
156156
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
157157
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
158-
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
159-
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
160-
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
161-
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
162-
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
163-
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
164-
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
165-
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
166-
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
167-
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
168-
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
169-
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
158+
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
159+
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
160+
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
161+
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
162+
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
163+
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
164+
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
165+
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
166+
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
167+
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
168+
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
169+
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
170170
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
171171
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
172172
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
@@ -221,14 +221,14 @@ gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E
221221
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
222222
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
223223
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
224-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
225-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
226-
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
227-
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
224+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
225+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
226+
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
227+
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
228228
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
229229
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
230-
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
231-
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
230+
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
231+
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
232232
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
233233
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
234234
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright IBM Corp. 2014, 2025
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package statestore_test
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"io"
11+
"io/fs"
12+
"path/filepath"
13+
"strings"
14+
"testing/fstest"
15+
"time"
16+
17+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
18+
"github.com/hashicorp/terraform-plugin-testing/internal/logging"
19+
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider"
20+
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/statestore"
21+
)
22+
23+
// This state store implementation uses an in-memory map for storing state in a filesystem-like manner.
24+
// This only works because the terraform-plugin-testing harness keeps provider instances running
25+
// throughout a Go test with multiple Terraform CLI command calls by using a reattach configuration.
26+
func exampleCloudValidStateStore() *testprovider.StateStore {
27+
memFS := fstest.MapFS{}
28+
29+
return &testprovider.StateStore{
30+
SchemaResponse: &statestore.SchemaResponse{
31+
Schema: &tfprotov6.Schema{
32+
Block: &tfprotov6.SchemaBlock{Attributes: []*tfprotov6.SchemaAttribute{}},
33+
},
34+
},
35+
GetStatesFunc: func(ctx context.Context, req statestore.GetStatesRequest, resp *statestore.GetStatesResponse) {
36+
directories, err := memFS.ReadDir(".")
37+
if err != nil {
38+
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
39+
Severity: tfprotov6.DiagnosticSeverityError,
40+
Summary: "Error reading inmem filesystem",
41+
Detail: err.Error(),
42+
})
43+
return
44+
}
45+
46+
workspaces := make([]string, 0)
47+
for _, dir := range directories {
48+
workspaces = append(workspaces, dir.Name())
49+
}
50+
51+
resp.StateIDs = workspaces
52+
},
53+
DeleteStateFunc: func(ctx context.Context, req statestore.DeleteStateRequest, resp *statestore.DeleteStateResponse) {
54+
for filePath := range memFS {
55+
if strings.HasPrefix(filePath, req.StateID) {
56+
delete(memFS, filePath)
57+
}
58+
}
59+
},
60+
LockStateFunc: func(ctx context.Context, req statestore.LockStateRequest, resp *statestore.LockStateResponse) {
61+
logging.HelperResourceDebug(ctx, "examplecloud_inmem: Lock support not implemented")
62+
},
63+
UnlockStateFunc: func(ctx context.Context, req statestore.UnlockStateRequest, resp *statestore.UnlockStateResponse) {
64+
logging.HelperResourceDebug(ctx, "examplecloud_inmem: Lock support not implemented")
65+
},
66+
ReadStateBytesFunc: func(ctx context.Context, req statestore.ReadStateBytesRequest, resp *statestore.ReadStateBytesResponse) {
67+
stateFilePath := filepath.Join(req.StateID, "terraform.tfstate")
68+
stateFile, err := memFS.Open(stateFilePath)
69+
if err != nil {
70+
// If there is no state file, Terraform will create one.
71+
if errors.Is(err, fs.ErrNotExist) {
72+
return
73+
}
74+
75+
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
76+
Severity: tfprotov6.DiagnosticSeverityError,
77+
Summary: fmt.Sprintf("Error reading state %q at path %q", req.StateID, stateFilePath),
78+
Detail: err.Error(),
79+
})
80+
return
81+
}
82+
83+
stateBytes, err := io.ReadAll(stateFile)
84+
if err != nil {
85+
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
86+
Severity: tfprotov6.DiagnosticSeverityError,
87+
Summary: fmt.Sprintf("Error reading %q state bytes", req.StateID),
88+
Detail: err.Error(),
89+
})
90+
return
91+
}
92+
93+
resp.StateBytes = stateBytes
94+
},
95+
WriteStateBytesFunc: func(ctx context.Context, req statestore.WriteStateBytesRequest, resp *statestore.WriteStateBytesResponse) {
96+
stateFilePath := filepath.Join(req.StateID, "terraform.tfstate")
97+
memFS[stateFilePath] = &fstest.MapFile{
98+
Data: req.StateBytes,
99+
Mode: fs.ModePerm,
100+
ModTime: time.Now(),
101+
}
102+
},
103+
}
104+
}

0 commit comments

Comments
 (0)