Skip to content

Commit e642d51

Browse files
statestore: Implement Lock and Unlock for state stores (#1266)
* initial implementation for lock/unlock * from/to tests * add fwserver tests * add initial lock info struct * build(deps): Bump github.com/hashicorp/copywrite in /tools (#1264) Bumps [github.com/hashicorp/copywrite](https://github.com/hashicorp/copywrite) from 0.22.0 to 0.24.0. - [Release notes](https://github.com/hashicorp/copywrite/releases) - [Commits](hashicorp/copywrite@v0.22.0...v0.24.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/copywrite dependency-version: 0.24.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * apply steph's suggestion from 1262 * tidy * package docs * spelling --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent c999217 commit e642d51

25 files changed

+1443
-12
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ toolchain go1.24.10
66

77
require (
88
github.com/google/go-cmp v0.7.0
9+
github.com/hashicorp/go-uuid v1.0.3
910
github.com/hashicorp/terraform-plugin-go v0.29.1-0.20260122204301-b0a6aca80fd7
1011
github.com/hashicorp/terraform-plugin-log v0.10.0
1112

@@ -16,7 +17,6 @@ require (
1617
github.com/golang/protobuf v1.5.4 // indirect
1718
github.com/hashicorp/go-hclog v1.6.3 // indirect
1819
github.com/hashicorp/go-plugin v1.7.0 // indirect
19-
github.com/hashicorp/go-uuid v1.0.3 // indirect
2020
github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
2121
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
2222
github.com/hashicorp/yamux v0.1.2 // indirect

internal/fromproto6/lockstate.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright IBM Corp. 2021, 2026
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto6
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
10+
11+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
12+
"github.com/hashicorp/terraform-plugin-framework/statestore"
13+
)
14+
15+
// LockStateRequest returns the *fwserver.LockStateRequest
16+
// equivalent of a *tfprotov6.LockStateRequest.
17+
func LockStateRequest(ctx context.Context, proto6 *tfprotov6.LockStateRequest, reqStateStore statestore.StateStore) *fwserver.LockStateRequest {
18+
if proto6 == nil {
19+
return nil
20+
}
21+
22+
return &fwserver.LockStateRequest{
23+
StateID: proto6.StateID,
24+
Operation: proto6.Operation,
25+
StateStore: reqStateStore,
26+
}
27+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright IBM Corp. 2021, 2026
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto6_test
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
12+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
13+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
14+
)
15+
16+
func TestLockStateRequest(t *testing.T) {
17+
t.Parallel()
18+
19+
testCases := map[string]struct {
20+
input *tfprotov6.LockStateRequest
21+
expected *fwserver.LockStateRequest
22+
}{
23+
"nil": {
24+
input: nil,
25+
expected: nil,
26+
},
27+
"empty": {
28+
input: &tfprotov6.LockStateRequest{},
29+
expected: &fwserver.LockStateRequest{},
30+
},
31+
"lockstate": {
32+
input: &tfprotov6.LockStateRequest{
33+
StateID: "test-state-123",
34+
Operation: "apply",
35+
},
36+
expected: &fwserver.LockStateRequest{
37+
StateID: "test-state-123",
38+
Operation: "apply",
39+
},
40+
},
41+
}
42+
43+
for name, testCase := range testCases {
44+
t.Run(name, func(t *testing.T) {
45+
t.Parallel()
46+
47+
got := fromproto6.LockStateRequest(context.Background(), testCase.input, nil)
48+
49+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
50+
t.Errorf("unexpected difference: %s", diff)
51+
}
52+
})
53+
}
54+
}

internal/fromproto6/unlockstate.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright IBM Corp. 2021, 2026
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto6
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
10+
11+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
12+
"github.com/hashicorp/terraform-plugin-framework/statestore"
13+
)
14+
15+
// UnlockStateRequest returns the *fwserver.UnlockStateRequest
16+
// equivalent of a *tfprotov6.UnlockStateRequest.
17+
func UnlockStateRequest(ctx context.Context, proto6 *tfprotov6.UnlockStateRequest, reqStateStore statestore.StateStore) *fwserver.UnlockStateRequest {
18+
if proto6 == nil {
19+
return nil
20+
}
21+
22+
return &fwserver.UnlockStateRequest{
23+
StateID: proto6.StateID,
24+
LockID: proto6.LockID,
25+
StateStore: reqStateStore,
26+
}
27+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright IBM Corp. 2021, 2026
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto6_test
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
12+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
13+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
14+
)
15+
16+
func TestUnlockStateRequest(t *testing.T) {
17+
t.Parallel()
18+
19+
testCases := map[string]struct {
20+
input *tfprotov6.UnlockStateRequest
21+
expected *fwserver.UnlockStateRequest
22+
}{
23+
"nil": {
24+
input: nil,
25+
expected: nil,
26+
},
27+
"empty": {
28+
input: &tfprotov6.UnlockStateRequest{},
29+
expected: &fwserver.UnlockStateRequest{},
30+
},
31+
"unlockstate": {
32+
input: &tfprotov6.UnlockStateRequest{
33+
StateID: "test-state-123",
34+
LockID: "test-lock-123",
35+
},
36+
expected: &fwserver.UnlockStateRequest{
37+
StateID: "test-state-123",
38+
LockID: "test-lock-123",
39+
},
40+
},
41+
}
42+
43+
for name, testCase := range testCases {
44+
t.Run(name, func(t *testing.T) {
45+
t.Parallel()
46+
47+
got := fromproto6.UnlockStateRequest(context.Background(), testCase.input, nil)
48+
49+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
50+
t.Errorf("unexpected difference: %s", diff)
51+
}
52+
})
53+
}
54+
}

internal/fwserver/server_configurestatestore.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
// we could expose this to provider developers in [statestore.InitializeResponse] if controlling
1919
// the chunk size is desired.
2020
type ConfigureStateStoreClientCapabilities struct {
21-
// ChunkSize is the client-requested size of state byte chunks that are sent between Terraform Core and
21+
// ChunkSize is the client-requested size of state byte chunks that are sent between Terraform Core and the provider.
2222
// The default chunk size in Terraform core is 8 MB.
2323
ChunkSize int64
2424
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright IBM Corp. 2021, 2026
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fwserver
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
10+
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
11+
"github.com/hashicorp/terraform-plugin-framework/statestore"
12+
)
13+
14+
type LockStateRequest struct {
15+
StateID string
16+
Operation string
17+
StateStore statestore.StateStore
18+
}
19+
20+
type LockStateResponse struct {
21+
LockID string
22+
Diagnostics diag.Diagnostics
23+
}
24+
25+
func (s *Server) LockState(ctx context.Context, req *LockStateRequest, resp *LockStateResponse) {
26+
if req == nil {
27+
return
28+
}
29+
30+
if stateStoreWithConfigure, ok := req.StateStore.(statestore.StateStoreWithConfigure); ok {
31+
logging.FrameworkTrace(ctx, "StateStore implements StateStoreWithConfigure")
32+
33+
configureReq := statestore.ConfigureRequest{
34+
StateStoreData: s.StateStoreConfigureData.StateStoreConfigureData,
35+
}
36+
configureResp := statestore.ConfigureResponse{}
37+
38+
logging.FrameworkTrace(ctx, "Calling provider defined StateStore Configure")
39+
stateStoreWithConfigure.Configure(ctx, configureReq, &configureResp)
40+
logging.FrameworkTrace(ctx, "Called provider defined StateStore Configure")
41+
42+
resp.Diagnostics.Append(configureResp.Diagnostics...)
43+
44+
if resp.Diagnostics.HasError() {
45+
return
46+
}
47+
}
48+
49+
lockReq := statestore.LockRequest{
50+
StateID: req.StateID,
51+
Operation: req.Operation,
52+
}
53+
54+
lockResp := statestore.LockResponse{}
55+
56+
logging.FrameworkTrace(ctx, "Calling provider defined StateStore Lock")
57+
req.StateStore.Lock(ctx, lockReq, &lockResp)
58+
logging.FrameworkTrace(ctx, "Called provider defined StateStore Lock")
59+
60+
resp.Diagnostics.Append(lockResp.Diagnostics...)
61+
if resp.Diagnostics.HasError() {
62+
return
63+
}
64+
65+
resp.LockID = lockResp.LockID
66+
}

0 commit comments

Comments
 (0)