Skip to content

Prototyping how new plugin protocol methods fit with pre-existing interfaces used to access state #36932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: radek/pluggable-backends-protocol
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
815 changes: 815 additions & 0 deletions docs/plugin-protocol/tfplugin5.10.proto

Large diffs are not rendered by default.

899 changes: 899 additions & 0 deletions docs/plugin-protocol/tfplugin6.10.proto

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions internal/backend/pluggable-state/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package pluggable_state

import (
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
grpc_statemgr "github.com/hashicorp/terraform/internal/states/grpc"
"github.com/hashicorp/terraform/internal/states/statemgr"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)

func NewPluggable(p providers.Interface, typeName string) backend.Backend {
return &Pluggable{
provider: p,
typeName: typeName,
}
}

var _ backend.Backend = &Pluggable{}

type Pluggable struct {
provider providers.Interface
typeName string
}

func (p *Pluggable) ConfigSchema() *configschema.Block {
schemaResp := p.provider.GetProviderSchema()
if schemaResp.StateStores == nil {
// No state stores
return nil
}
val, ok := schemaResp.StateStores[p.typeName]
if !ok {
// Cannot find state store with that type
return nil
}

// State store type exists
return val.Body
}

func (p *Pluggable) PrepareConfig(config cty.Value) (cty.Value, tfdiags.Diagnostics) {
req := providers.ValidateStorageConfigRequest{
TypeName: p.typeName,
Config: config,
}
resp := p.provider.ValidateStorageConfig(req)
return config, resp.Diagnostics
}

func (p *Pluggable) Configure(config cty.Value) tfdiags.Diagnostics {
return nil
}

func (p *Pluggable) Workspaces() ([]string, error) {
return nil, nil
}

func (p *Pluggable) DeleteWorkspace(workspace string, force bool) error {
return nil
}

func (p *Pluggable) StateMgr(workspace string) (statemgr.Full, error) {
// repackages the provider's methods inside a state manager,
// to be passed to the calling code that expects a statemgr.Full
return grpc_statemgr.NewGrpcStateManager(p.provider, p.typeName, workspace), nil
}
1 change: 1 addition & 0 deletions internal/builtin/providers/terraform/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func (p *Provider) GetProviderSchema() providers.GetProviderSchemaResponse {
ReturnType: cty.String,
},
},
StateStores: map[string]providers.Schema{},
}
providers.SchemaCache.Set(tfaddr.NewProvider(tfaddr.BuiltInProviderHost, tfaddr.BuiltInProviderNamespace, "terraform"), resp)
return resp
Expand Down
46 changes: 46 additions & 0 deletions internal/builtin/providers/terraform/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package terraform

import (
"fmt"

"github.com/hashicorp/terraform/internal/providers"
)

func (p *Provider) ValidateStorageConfig(req providers.ValidateStorageConfigRequest) providers.ValidateStorageConfigResponse {
var resp providers.ValidateStorageConfigResponse
resp.Diagnostics.Append(fmt.Errorf("unsupported storage type %q", req.TypeName))
return resp
}

func (p *Provider) ConfigureStorage(req providers.ConfigureStorageRequest) providers.ConfigureStorageResponse {
var resp providers.ConfigureStorageResponse
resp.Diagnostics.Append(fmt.Errorf("unsupported storage type %q", req.TypeName))
return resp
}

func (p *Provider) LockState(req providers.LockStateRequest) providers.LockStateResponse {
var resp providers.LockStateResponse
resp.Diagnostics.Append(fmt.Errorf("unsupported storage type %q", req.TypeName))
return resp
}

func (p *Provider) UnlockState(req providers.UnlockStateRequest) providers.UnlockStateResponse {
var resp providers.UnlockStateResponse
resp.Diagnostics.Append(fmt.Errorf("unsupported storage type %q", req.TypeName))
return resp
}

func (p *Provider) GetStates(req providers.GetStatesRequest) providers.GetStatesResponse {
var resp providers.GetStatesResponse
resp.StateIds = nil // No states when no state storage is implemented, even `default`
return resp
}

func (p *Provider) DeleteState(req providers.DeleteStateRequest) providers.DeleteStateResponse {
var resp providers.DeleteStateResponse
resp.Diagnostics.Append(fmt.Errorf("unsupported storage type %q", req.TypeName))
return resp
}
28 changes: 19 additions & 9 deletions internal/command/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend"
backendInit "github.com/hashicorp/terraform/internal/backend/init"
"github.com/hashicorp/terraform/internal/backend/local"
pluggable_state "github.com/hashicorp/terraform/internal/backend/pluggable-state"
"github.com/hashicorp/terraform/internal/cloud"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/views"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/getproviders"
simple "github.com/hashicorp/terraform/internal/provider-simple-v6"
"github.com/hashicorp/terraform/internal/providercache"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/terraform"
Expand Down Expand Up @@ -180,15 +183,22 @@ func (c *InitCommand) Run(args []string) int {
var backDiags tfdiags.Diagnostics
var backendOutput bool

switch {
case initArgs.Cloud && rootModEarly.CloudConfig != nil:
back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view)
case initArgs.Backend:
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view)
default:
// load the previously-stored backend config
back, backDiags = c.Meta.backendFromState(ctx)
}
spv6 := simple.Provider()
// Which state storage implementation to use from the provider
typeName := "local"
// How do we convert a provider into a Backend interface?
stateStorage := pluggable_state.NewPluggable(spv6, typeName)
back = local.NewWithBackend(stateStorage)

// switch {
// case initArgs.Cloud && rootModEarly.CloudConfig != nil:
// back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view)
// case initArgs.Backend:
// back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view)
// default:
// // load the previously-stored backend config
// back, backDiags = c.Meta.backendFromState(ctx)
// }
if backendOutput {
header = true
}
Expand Down
1 change: 1 addition & 0 deletions internal/command/testing/test_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ var (
ReturnType: cty.Bool,
},
},
// TODO - add a State Stores map here?
}
)

Expand Down
7 changes: 7 additions & 0 deletions internal/grpcwrap/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
ResourceSchemas: make(map[string]*tfplugin5.Schema),
DataSourceSchemas: make(map[string]*tfplugin5.Schema),
EphemeralResourceSchemas: make(map[string]*tfplugin5.Schema),
StateStoreSchemas: make(map[string]*tfplugin5.Schema),
}

resp.Provider = &tfplugin5.Schema{
Expand Down Expand Up @@ -79,6 +80,12 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
for typ, dat := range p.schema.StateStores {
resp.StateStoreSchemas[typ] = &tfplugin5.Schema{
Version: int64(dat.Version),
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
if decls, err := convert.FunctionDeclsToProto(p.schema.Functions); err == nil {
resp.Functions = decls
} else {
Expand Down
47 changes: 47 additions & 0 deletions internal/grpcwrap/provider6.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func (p *provider6) GetProviderSchema(_ context.Context, req *tfplugin6.GetProvi
DataSourceSchemas: make(map[string]*tfplugin6.Schema),
EphemeralResourceSchemas: make(map[string]*tfplugin6.Schema),
Functions: make(map[string]*tfplugin6.Function),
StateStoreSchemas: make(map[string]*tfplugin6.Schema),
}

resp.Provider = &tfplugin6.Schema{
Expand Down Expand Up @@ -81,6 +82,12 @@ func (p *provider6) GetProviderSchema(_ context.Context, req *tfplugin6.GetProvi
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
for typ, dat := range p.schema.StateStores {
resp.StateStoreSchemas[typ] = &tfplugin6.Schema{
Version: int64(dat.Version),
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
if decls, err := convert.FunctionDeclsToProto(p.schema.Functions); err == nil {
resp.Functions = decls
} else {
Expand Down Expand Up @@ -784,6 +791,46 @@ func (p *provider6) UpgradeResourceIdentity(_ context.Context, req *tfplugin6.Up
return resp, nil
}

func (p *provider6) ValidateStorageConfig(ctx context.Context, req *tfplugin6.ValidateStorage_Request) (*tfplugin6.ValidateStorage_Response, error) {
// TODO
return nil, nil
}

func (p *provider6) ConfigureStorage(ctx context.Context, req *tfplugin6.ConfigureStorage_Request) (*tfplugin6.ConfigureStorage_Response, error) {
// TODO
return nil, nil
}

func (p *provider6) ReadState(req *tfplugin6.ReadState_Request, srv tfplugin6.Provider_ReadStateServer) error {
// TODO
return nil
}

func (p *provider6) WriteState(srv tfplugin6.Provider_WriteStateServer) error {
// TODO
return nil
}

func (p *provider6) LockState(ctx context.Context, req *tfplugin6.LockState_Request) (*tfplugin6.LockState_Response, error) {
// TODO
return nil, nil
}

func (p *provider6) UnlockState(ctx context.Context, req *tfplugin6.UnlockState_Request) (*tfplugin6.UnlockState_Response, error) {
// TODO
return nil, nil
}

func (p *provider6) GetStates(ctx context.Context, req *tfplugin6.GetStates_Request) (*tfplugin6.GetStates_Response, error) {
// TODO
return nil, nil
}

func (p *provider6) DeleteState(ctx context.Context, req *tfplugin6.DeleteState_Request) (*tfplugin6.DeleteState_Response, error) {
// TODO
return nil, nil
}

func (p *provider6) StopProvider(context.Context, *tfplugin6.StopProvider_Request) (*tfplugin6.StopProvider_Response, error) {
resp := &tfplugin6.StopProvider_Response{}
err := p.provider.Stop()
Expand Down
33 changes: 33 additions & 0 deletions internal/plugin/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func (p *GRPCProviderPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Serve
return nil
}

var _ providers.Interface = &GRPCProvider{}

// GRPCProvider handles the client, or core side of the plugin rpc connection.
// The GRPCProvider methods are mostly a translation layer between the
// terraform providers types and the grpc proto types, directly converting
Expand Down Expand Up @@ -101,6 +103,7 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
resp.ResourceTypes = make(map[string]providers.Schema)
resp.DataSources = make(map[string]providers.Schema)
resp.EphemeralResourceTypes = make(map[string]providers.Schema)
// TODO: resp.StateStores = make(map[string]providers.Schema)

// Some providers may generate quite large schemas, and the internal default
// grpc response size limit is 4MB. 64MB should cover most any use case, and
Expand Down Expand Up @@ -1251,3 +1254,33 @@ func clientCapabilitiesToProto(c providers.ClientCapabilities) *proto.ClientCapa
WriteOnlyAttributesAllowed: c.WriteOnlyAttributesAllowed,
}
}

func (p *GRPCProvider) ValidateStorageConfig(r providers.ValidateStorageConfigRequest) providers.ValidateStorageConfigResponse {
// TODO
return providers.ValidateStorageConfigResponse{}
}

func (p *GRPCProvider) ConfigureStorage(r providers.ConfigureStorageRequest) providers.ConfigureStorageResponse {
// TODO
return providers.ConfigureStorageResponse{}
}

func (p *GRPCProvider) LockState(req providers.LockStateRequest) providers.LockStateResponse {
// TODO
return providers.LockStateResponse{}
}

func (p *GRPCProvider) UnlockState(req providers.UnlockStateRequest) providers.UnlockStateResponse {
// TODO
return providers.UnlockStateResponse{}
}

func (p *GRPCProvider) GetStates(req providers.GetStatesRequest) providers.GetStatesResponse {
// TODO
return providers.GetStatesResponse{}
}

func (p *GRPCProvider) DeleteState(req providers.DeleteStateRequest) providers.DeleteStateResponse {
// TODO
return providers.DeleteStateResponse{}
}
32 changes: 32 additions & 0 deletions internal/plugin6/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func (p *GRPCProviderPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Serve
return nil
}

var _ providers.Interface = &GRPCProvider{}

// GRPCProvider handles the client, or core side of the plugin rpc connection.
// The GRPCProvider methods are mostly a translation layer between the
// terraform providers types and the grpc proto types, directly converting
Expand Down Expand Up @@ -1196,6 +1198,36 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
return resp
}

func (p *GRPCProvider) ValidateStorageConfig(r providers.ValidateStorageConfigRequest) providers.ValidateStorageConfigResponse {
// TODO
return providers.ValidateStorageConfigResponse{}
}

func (p *GRPCProvider) ConfigureStorage(r providers.ConfigureStorageRequest) providers.ConfigureStorageResponse {
// TODO
return providers.ConfigureStorageResponse{}
}

func (p *GRPCProvider) LockState(req providers.LockStateRequest) providers.LockStateResponse {
// TODO
return providers.LockStateResponse{}
}

func (p *GRPCProvider) UnlockState(req providers.UnlockStateRequest) providers.UnlockStateResponse {
// TODO
return providers.UnlockStateResponse{}
}

func (p *GRPCProvider) GetStates(req providers.GetStatesRequest) providers.GetStatesResponse {
// TODO
return providers.GetStatesResponse{}
}

func (p *GRPCProvider) DeleteState(req providers.DeleteStateRequest) providers.DeleteStateResponse {
// TODO
return providers.DeleteStateResponse{}
}

// closing the grpc connection is final, and terraform will call it at the end of every phase.
func (p *GRPCProvider) Close() error {
logger.Trace("GRPCProvider.v6: Close")
Expand Down
Loading
Loading