opentofu/plugin/resource_provider.go
Martin Atkins 0e0e3d73af core: New ResourceProvider methods for data resources
This is a breaking change to the ResourceProvider interface that adds the
new operations relating to data sources.

DataSources, ValidateDataSource, ReadDataDiff and ReadDataApply are the
data source equivalents of Resources, Validate, Diff and Apply (respectively)
for managed resources.

The diff/apply model seems at first glance a rather strange workflow for
read-only resources, but implementing data resources in this way allows them
to fit cleanly into the standard plan/apply lifecycle in cases where the
configuration contains computed arguments and thus the read must be deferred
until apply time.

Along with breaking the interface, we also fix up the plugin client/server
and helper/schema implementations of it, which are all of the callers
used when provider plugins use helper/schema. This would be a breaking
change for any provider plugin that directly implements the provider
interface, but no known plugins do this and it is not recommended.

At the helper/schema layer the implementer sees ReadDataApply as a "Read",
as opposed to "Create" or "Update" as in the managed resource Apply
implementation. The planning mechanics are handled entirely within
helper/schema, so that complexity is hidden from the provider implementation
itself.
2016-05-14 08:26:36 -07:00

551 lines
12 KiB
Go

package plugin
import (
"net/rpc"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/terraform"
)
// ResourceProviderPlugin is the plugin.Plugin implementation.
type ResourceProviderPlugin struct {
F func() terraform.ResourceProvider
}
func (p *ResourceProviderPlugin) Server(b *plugin.MuxBroker) (interface{}, error) {
return &ResourceProviderServer{Broker: b, Provider: p.F()}, nil
}
func (p *ResourceProviderPlugin) Client(
b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &ResourceProvider{Broker: b, Client: c}, nil
}
// ResourceProvider is an implementation of terraform.ResourceProvider
// that communicates over RPC.
type ResourceProvider struct {
Broker *plugin.MuxBroker
Client *rpc.Client
}
func (p *ResourceProvider) Input(
input terraform.UIInput,
c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
id := p.Broker.NextId()
go p.Broker.AcceptAndServe(id, &UIInputServer{
UIInput: input,
})
var resp ResourceProviderInputResponse
args := ResourceProviderInputArgs{
InputId: id,
Config: c,
}
err := p.Client.Call("Plugin.Input", &args, &resp)
if err != nil {
return nil, err
}
if resp.Error != nil {
err = resp.Error
return nil, err
}
return resp.Config, nil
}
func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
var resp ResourceProviderValidateResponse
args := ResourceProviderValidateArgs{
Config: c,
}
err := p.Client.Call("Plugin.Validate", &args, &resp)
if err != nil {
return nil, []error{err}
}
var errs []error
if len(resp.Errors) > 0 {
errs = make([]error, len(resp.Errors))
for i, err := range resp.Errors {
errs[i] = err
}
}
return resp.Warnings, errs
}
func (p *ResourceProvider) ValidateResource(
t string, c *terraform.ResourceConfig) ([]string, []error) {
var resp ResourceProviderValidateResourceResponse
args := ResourceProviderValidateResourceArgs{
Config: c,
Type: t,
}
err := p.Client.Call("Plugin.ValidateResource", &args, &resp)
if err != nil {
return nil, []error{err}
}
var errs []error
if len(resp.Errors) > 0 {
errs = make([]error, len(resp.Errors))
for i, err := range resp.Errors {
errs[i] = err
}
}
return resp.Warnings, errs
}
func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error {
var resp ResourceProviderConfigureResponse
err := p.Client.Call("Plugin.Configure", c, &resp)
if err != nil {
return err
}
if resp.Error != nil {
err = resp.Error
}
return err
}
func (p *ResourceProvider) Apply(
info *terraform.InstanceInfo,
s *terraform.InstanceState,
d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
var resp ResourceProviderApplyResponse
args := &ResourceProviderApplyArgs{
Info: info,
State: s,
Diff: d,
}
err := p.Client.Call("Plugin.Apply", args, &resp)
if err != nil {
return nil, err
}
if resp.Error != nil {
err = resp.Error
}
return resp.State, err
}
func (p *ResourceProvider) Diff(
info *terraform.InstanceInfo,
s *terraform.InstanceState,
c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
var resp ResourceProviderDiffResponse
args := &ResourceProviderDiffArgs{
Info: info,
State: s,
Config: c,
}
err := p.Client.Call("Plugin.Diff", args, &resp)
if err != nil {
return nil, err
}
if resp.Error != nil {
err = resp.Error
}
return resp.Diff, err
}
func (p *ResourceProvider) ValidateDataSource(
t string, c *terraform.ResourceConfig) ([]string, []error) {
var resp ResourceProviderValidateResourceResponse
args := ResourceProviderValidateResourceArgs{
Config: c,
Type: t,
}
err := p.Client.Call("Plugin.ValidateDataSource", &args, &resp)
if err != nil {
return nil, []error{err}
}
var errs []error
if len(resp.Errors) > 0 {
errs = make([]error, len(resp.Errors))
for i, err := range resp.Errors {
errs[i] = err
}
}
return resp.Warnings, errs
}
func (p *ResourceProvider) Refresh(
info *terraform.InstanceInfo,
s *terraform.InstanceState) (*terraform.InstanceState, error) {
var resp ResourceProviderRefreshResponse
args := &ResourceProviderRefreshArgs{
Info: info,
State: s,
}
err := p.Client.Call("Plugin.Refresh", args, &resp)
if err != nil {
return nil, err
}
if resp.Error != nil {
err = resp.Error
}
return resp.State, err
}
func (p *ResourceProvider) ImportState(
info *terraform.InstanceInfo,
id string) ([]*terraform.InstanceState, error) {
var resp ResourceProviderImportStateResponse
args := &ResourceProviderImportStateArgs{
Info: info,
Id: id,
}
err := p.Client.Call("Plugin.ImportState", args, &resp)
if err != nil {
return nil, err
}
if resp.Error != nil {
err = resp.Error
}
return resp.State, err
}
func (p *ResourceProvider) Resources() []terraform.ResourceType {
var result []terraform.ResourceType
err := p.Client.Call("Plugin.Resources", new(interface{}), &result)
if err != nil {
// TODO: panic, log, what?
return nil
}
return result
}
func (p *ResourceProvider) ReadDataDiff(
info *terraform.InstanceInfo,
c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
var resp ResourceProviderReadDataDiffResponse
args := &ResourceProviderReadDataDiffArgs{
Info: info,
Config: c,
}
err := p.Client.Call("Plugin.ReadDataDiff", args, &resp)
if err != nil {
return nil, err
}
if resp.Error != nil {
err = resp.Error
}
return resp.Diff, err
}
func (p *ResourceProvider) ReadDataApply(
info *terraform.InstanceInfo,
d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
var resp ResourceProviderReadDataApplyResponse
args := &ResourceProviderReadDataApplyArgs{
Info: info,
Diff: d,
}
err := p.Client.Call("Plugin.ReadDataApply", args, &resp)
if err != nil {
return nil, err
}
if resp.Error != nil {
err = resp.Error
}
return resp.State, err
}
func (p *ResourceProvider) DataSources() []terraform.DataSource {
var result []terraform.DataSource
err := p.Client.Call("Plugin.DataSources", new(interface{}), &result)
if err != nil {
// TODO: panic, log, what?
return nil
}
return result
}
func (p *ResourceProvider) Close() error {
return p.Client.Close()
}
// ResourceProviderServer is a net/rpc compatible structure for serving
// a ResourceProvider. This should not be used directly.
type ResourceProviderServer struct {
Broker *plugin.MuxBroker
Provider terraform.ResourceProvider
}
type ResourceProviderConfigureResponse struct {
Error *plugin.BasicError
}
type ResourceProviderInputArgs struct {
InputId uint32
Config *terraform.ResourceConfig
}
type ResourceProviderInputResponse struct {
Config *terraform.ResourceConfig
Error *plugin.BasicError
}
type ResourceProviderApplyArgs struct {
Info *terraform.InstanceInfo
State *terraform.InstanceState
Diff *terraform.InstanceDiff
}
type ResourceProviderApplyResponse struct {
State *terraform.InstanceState
Error *plugin.BasicError
}
type ResourceProviderDiffArgs struct {
Info *terraform.InstanceInfo
State *terraform.InstanceState
Config *terraform.ResourceConfig
}
type ResourceProviderDiffResponse struct {
Diff *terraform.InstanceDiff
Error *plugin.BasicError
}
type ResourceProviderRefreshArgs struct {
Info *terraform.InstanceInfo
State *terraform.InstanceState
}
type ResourceProviderRefreshResponse struct {
State *terraform.InstanceState
Error *plugin.BasicError
}
type ResourceProviderImportStateArgs struct {
Info *terraform.InstanceInfo
Id string
}
type ResourceProviderImportStateResponse struct {
State []*terraform.InstanceState
Error *plugin.BasicError
}
type ResourceProviderReadDataApplyArgs struct {
Info *terraform.InstanceInfo
Diff *terraform.InstanceDiff
}
type ResourceProviderReadDataApplyResponse struct {
State *terraform.InstanceState
Error *plugin.BasicError
}
type ResourceProviderReadDataDiffArgs struct {
Info *terraform.InstanceInfo
Config *terraform.ResourceConfig
}
type ResourceProviderReadDataDiffResponse struct {
Diff *terraform.InstanceDiff
Error *plugin.BasicError
}
type ResourceProviderValidateArgs struct {
Config *terraform.ResourceConfig
}
type ResourceProviderValidateResponse struct {
Warnings []string
Errors []*plugin.BasicError
}
type ResourceProviderValidateResourceArgs struct {
Config *terraform.ResourceConfig
Type string
}
type ResourceProviderValidateResourceResponse struct {
Warnings []string
Errors []*plugin.BasicError
}
func (s *ResourceProviderServer) Input(
args *ResourceProviderInputArgs,
reply *ResourceProviderInputResponse) error {
conn, err := s.Broker.Dial(args.InputId)
if err != nil {
*reply = ResourceProviderInputResponse{
Error: plugin.NewBasicError(err),
}
return nil
}
client := rpc.NewClient(conn)
defer client.Close()
input := &UIInput{Client: client}
config, err := s.Provider.Input(input, args.Config)
*reply = ResourceProviderInputResponse{
Config: config,
Error: plugin.NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) Validate(
args *ResourceProviderValidateArgs,
reply *ResourceProviderValidateResponse) error {
warns, errs := s.Provider.Validate(args.Config)
berrs := make([]*plugin.BasicError, len(errs))
for i, err := range errs {
berrs[i] = plugin.NewBasicError(err)
}
*reply = ResourceProviderValidateResponse{
Warnings: warns,
Errors: berrs,
}
return nil
}
func (s *ResourceProviderServer) ValidateResource(
args *ResourceProviderValidateResourceArgs,
reply *ResourceProviderValidateResourceResponse) error {
warns, errs := s.Provider.ValidateResource(args.Type, args.Config)
berrs := make([]*plugin.BasicError, len(errs))
for i, err := range errs {
berrs[i] = plugin.NewBasicError(err)
}
*reply = ResourceProviderValidateResourceResponse{
Warnings: warns,
Errors: berrs,
}
return nil
}
func (s *ResourceProviderServer) Configure(
config *terraform.ResourceConfig,
reply *ResourceProviderConfigureResponse) error {
err := s.Provider.Configure(config)
*reply = ResourceProviderConfigureResponse{
Error: plugin.NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) Apply(
args *ResourceProviderApplyArgs,
result *ResourceProviderApplyResponse) error {
state, err := s.Provider.Apply(args.Info, args.State, args.Diff)
*result = ResourceProviderApplyResponse{
State: state,
Error: plugin.NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) Diff(
args *ResourceProviderDiffArgs,
result *ResourceProviderDiffResponse) error {
diff, err := s.Provider.Diff(args.Info, args.State, args.Config)
*result = ResourceProviderDiffResponse{
Diff: diff,
Error: plugin.NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) Refresh(
args *ResourceProviderRefreshArgs,
result *ResourceProviderRefreshResponse) error {
newState, err := s.Provider.Refresh(args.Info, args.State)
*result = ResourceProviderRefreshResponse{
State: newState,
Error: plugin.NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) ImportState(
args *ResourceProviderImportStateArgs,
result *ResourceProviderImportStateResponse) error {
states, err := s.Provider.ImportState(args.Info, args.Id)
*result = ResourceProviderImportStateResponse{
State: states,
Error: plugin.NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) Resources(
nothing interface{},
result *[]terraform.ResourceType) error {
*result = s.Provider.Resources()
return nil
}
func (s *ResourceProviderServer) ValidateDataSource(
args *ResourceProviderValidateResourceArgs,
reply *ResourceProviderValidateResourceResponse) error {
warns, errs := s.Provider.ValidateDataSource(args.Type, args.Config)
berrs := make([]*plugin.BasicError, len(errs))
for i, err := range errs {
berrs[i] = plugin.NewBasicError(err)
}
*reply = ResourceProviderValidateResourceResponse{
Warnings: warns,
Errors: berrs,
}
return nil
}
func (s *ResourceProviderServer) ReadDataDiff(
args *ResourceProviderReadDataDiffArgs,
result *ResourceProviderReadDataDiffResponse) error {
diff, err := s.Provider.ReadDataDiff(args.Info, args.Config)
*result = ResourceProviderReadDataDiffResponse{
Diff: diff,
Error: plugin.NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) ReadDataApply(
args *ResourceProviderReadDataApplyArgs,
result *ResourceProviderReadDataApplyResponse) error {
newState, err := s.Provider.ReadDataApply(args.Info, args.Diff)
*result = ResourceProviderReadDataApplyResponse{
State: newState,
Error: plugin.NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) DataSources(
nothing interface{},
result *[]terraform.DataSource) error {
*result = s.Provider.DataSources()
return nil
}