github.com/opentofu/opentofu@v1.7.1/internal/grpcwrap/provider.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package grpcwrap
     7  
     8  import (
     9  	"context"
    10  
    11  	"github.com/opentofu/opentofu/internal/plugin/convert"
    12  	"github.com/opentofu/opentofu/internal/providers"
    13  	"github.com/opentofu/opentofu/internal/tfplugin5"
    14  	"github.com/zclconf/go-cty/cty"
    15  	ctyjson "github.com/zclconf/go-cty/cty/json"
    16  	"github.com/zclconf/go-cty/cty/msgpack"
    17  )
    18  
    19  // New wraps a providers.Interface to implement a grpc ProviderServer.
    20  // This is useful for creating a test binary out of an internal provider
    21  // implementation.
    22  func Provider(p providers.Interface) tfplugin5.ProviderServer {
    23  	return &provider{
    24  		provider: p,
    25  		schema:   p.GetProviderSchema(),
    26  	}
    27  }
    28  
    29  type provider struct {
    30  	provider providers.Interface
    31  	schema   providers.GetProviderSchemaResponse
    32  }
    33  
    34  func (p *provider) GetMetadata(context.Context, *tfplugin5.GetMetadata_Request) (*tfplugin5.GetMetadata_Response, error) {
    35  	panic("Not Implemented")
    36  }
    37  
    38  func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema_Request) (*tfplugin5.GetProviderSchema_Response, error) {
    39  	resp := &tfplugin5.GetProviderSchema_Response{
    40  		ResourceSchemas:   make(map[string]*tfplugin5.Schema),
    41  		DataSourceSchemas: make(map[string]*tfplugin5.Schema),
    42  	}
    43  
    44  	resp.Provider = &tfplugin5.Schema{
    45  		Block: &tfplugin5.Schema_Block{},
    46  	}
    47  	if p.schema.Provider.Block != nil {
    48  		resp.Provider.Block = convert.ConfigSchemaToProto(p.schema.Provider.Block)
    49  	}
    50  
    51  	resp.ProviderMeta = &tfplugin5.Schema{
    52  		Block: &tfplugin5.Schema_Block{},
    53  	}
    54  	if p.schema.ProviderMeta.Block != nil {
    55  		resp.ProviderMeta.Block = convert.ConfigSchemaToProto(p.schema.ProviderMeta.Block)
    56  	}
    57  
    58  	for typ, res := range p.schema.ResourceTypes {
    59  		resp.ResourceSchemas[typ] = &tfplugin5.Schema{
    60  			Version: res.Version,
    61  			Block:   convert.ConfigSchemaToProto(res.Block),
    62  		}
    63  	}
    64  	for typ, dat := range p.schema.DataSources {
    65  		resp.DataSourceSchemas[typ] = &tfplugin5.Schema{
    66  			Version: dat.Version,
    67  			Block:   convert.ConfigSchemaToProto(dat.Block),
    68  		}
    69  	}
    70  
    71  	resp.ServerCapabilities = &tfplugin5.ServerCapabilities{
    72  		PlanDestroy: p.schema.ServerCapabilities.PlanDestroy,
    73  	}
    74  
    75  	// include any diagnostics from the original GetSchema call
    76  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, p.schema.Diagnostics)
    77  
    78  	return resp, nil
    79  }
    80  
    81  func (p *provider) PrepareProviderConfig(_ context.Context, req *tfplugin5.PrepareProviderConfig_Request) (*tfplugin5.PrepareProviderConfig_Response, error) {
    82  	resp := &tfplugin5.PrepareProviderConfig_Response{}
    83  	ty := p.schema.Provider.Block.ImpliedType()
    84  
    85  	configVal, err := decodeDynamicValue(req.Config, ty)
    86  	if err != nil {
    87  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
    88  		return resp, nil
    89  	}
    90  
    91  	prepareResp := p.provider.ValidateProviderConfig(providers.ValidateProviderConfigRequest{
    92  		Config: configVal,
    93  	})
    94  
    95  	// the PreparedConfig value is no longer used
    96  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, prepareResp.Diagnostics)
    97  	return resp, nil
    98  }
    99  
   100  func (p *provider) ValidateResourceTypeConfig(_ context.Context, req *tfplugin5.ValidateResourceTypeConfig_Request) (*tfplugin5.ValidateResourceTypeConfig_Response, error) {
   101  	resp := &tfplugin5.ValidateResourceTypeConfig_Response{}
   102  	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
   103  
   104  	configVal, err := decodeDynamicValue(req.Config, ty)
   105  	if err != nil {
   106  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   107  		return resp, nil
   108  	}
   109  
   110  	validateResp := p.provider.ValidateResourceConfig(providers.ValidateResourceConfigRequest{
   111  		TypeName: req.TypeName,
   112  		Config:   configVal,
   113  	})
   114  
   115  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
   116  	return resp, nil
   117  }
   118  
   119  func (p *provider) ValidateDataSourceConfig(_ context.Context, req *tfplugin5.ValidateDataSourceConfig_Request) (*tfplugin5.ValidateDataSourceConfig_Response, error) {
   120  	resp := &tfplugin5.ValidateDataSourceConfig_Response{}
   121  	ty := p.schema.DataSources[req.TypeName].Block.ImpliedType()
   122  
   123  	configVal, err := decodeDynamicValue(req.Config, ty)
   124  	if err != nil {
   125  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   126  		return resp, nil
   127  	}
   128  
   129  	validateResp := p.provider.ValidateDataResourceConfig(providers.ValidateDataResourceConfigRequest{
   130  		TypeName: req.TypeName,
   131  		Config:   configVal,
   132  	})
   133  
   134  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
   135  	return resp, nil
   136  }
   137  
   138  func (p *provider) UpgradeResourceState(_ context.Context, req *tfplugin5.UpgradeResourceState_Request) (*tfplugin5.UpgradeResourceState_Response, error) {
   139  	resp := &tfplugin5.UpgradeResourceState_Response{}
   140  	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
   141  
   142  	upgradeResp := p.provider.UpgradeResourceState(providers.UpgradeResourceStateRequest{
   143  		TypeName:     req.TypeName,
   144  		Version:      req.Version,
   145  		RawStateJSON: req.RawState.Json,
   146  	})
   147  
   148  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, upgradeResp.Diagnostics)
   149  	if upgradeResp.Diagnostics.HasErrors() {
   150  		return resp, nil
   151  	}
   152  
   153  	dv, err := encodeDynamicValue(upgradeResp.UpgradedState, ty)
   154  	if err != nil {
   155  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   156  		return resp, nil
   157  	}
   158  
   159  	resp.UpgradedState = dv
   160  
   161  	return resp, nil
   162  }
   163  
   164  func (p *provider) Configure(_ context.Context, req *tfplugin5.Configure_Request) (*tfplugin5.Configure_Response, error) {
   165  	resp := &tfplugin5.Configure_Response{}
   166  	ty := p.schema.Provider.Block.ImpliedType()
   167  
   168  	configVal, err := decodeDynamicValue(req.Config, ty)
   169  	if err != nil {
   170  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   171  		return resp, nil
   172  	}
   173  
   174  	configureResp := p.provider.ConfigureProvider(providers.ConfigureProviderRequest{
   175  		TerraformVersion: req.TerraformVersion,
   176  		Config:           configVal,
   177  	})
   178  
   179  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, configureResp.Diagnostics)
   180  	return resp, nil
   181  }
   182  
   183  func (p *provider) ReadResource(_ context.Context, req *tfplugin5.ReadResource_Request) (*tfplugin5.ReadResource_Response, error) {
   184  	resp := &tfplugin5.ReadResource_Response{}
   185  	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
   186  
   187  	stateVal, err := decodeDynamicValue(req.CurrentState, ty)
   188  	if err != nil {
   189  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   190  		return resp, nil
   191  	}
   192  
   193  	metaTy := p.schema.ProviderMeta.Block.ImpliedType()
   194  	metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy)
   195  	if err != nil {
   196  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   197  		return resp, nil
   198  	}
   199  
   200  	readResp := p.provider.ReadResource(providers.ReadResourceRequest{
   201  		TypeName:     req.TypeName,
   202  		PriorState:   stateVal,
   203  		Private:      req.Private,
   204  		ProviderMeta: metaVal,
   205  	})
   206  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics)
   207  	if readResp.Diagnostics.HasErrors() {
   208  		return resp, nil
   209  	}
   210  	resp.Private = readResp.Private
   211  
   212  	dv, err := encodeDynamicValue(readResp.NewState, ty)
   213  	if err != nil {
   214  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   215  		return resp, nil
   216  	}
   217  	resp.NewState = dv
   218  
   219  	return resp, nil
   220  }
   221  
   222  func (p *provider) PlanResourceChange(_ context.Context, req *tfplugin5.PlanResourceChange_Request) (*tfplugin5.PlanResourceChange_Response, error) {
   223  	resp := &tfplugin5.PlanResourceChange_Response{}
   224  	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
   225  
   226  	priorStateVal, err := decodeDynamicValue(req.PriorState, ty)
   227  	if err != nil {
   228  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   229  		return resp, nil
   230  	}
   231  
   232  	proposedStateVal, err := decodeDynamicValue(req.ProposedNewState, ty)
   233  	if err != nil {
   234  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   235  		return resp, nil
   236  	}
   237  
   238  	configVal, err := decodeDynamicValue(req.Config, ty)
   239  	if err != nil {
   240  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   241  		return resp, nil
   242  	}
   243  
   244  	metaTy := p.schema.ProviderMeta.Block.ImpliedType()
   245  	metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy)
   246  	if err != nil {
   247  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   248  		return resp, nil
   249  	}
   250  
   251  	planResp := p.provider.PlanResourceChange(providers.PlanResourceChangeRequest{
   252  		TypeName:         req.TypeName,
   253  		PriorState:       priorStateVal,
   254  		ProposedNewState: proposedStateVal,
   255  		Config:           configVal,
   256  		PriorPrivate:     req.PriorPrivate,
   257  		ProviderMeta:     metaVal,
   258  	})
   259  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics)
   260  	if planResp.Diagnostics.HasErrors() {
   261  		return resp, nil
   262  	}
   263  
   264  	resp.PlannedPrivate = planResp.PlannedPrivate
   265  
   266  	resp.PlannedState, err = encodeDynamicValue(planResp.PlannedState, ty)
   267  	if err != nil {
   268  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   269  		return resp, nil
   270  	}
   271  
   272  	for _, path := range planResp.RequiresReplace {
   273  		resp.RequiresReplace = append(resp.RequiresReplace, convert.PathToAttributePath(path))
   274  	}
   275  
   276  	return resp, nil
   277  }
   278  
   279  func (p *provider) ApplyResourceChange(_ context.Context, req *tfplugin5.ApplyResourceChange_Request) (*tfplugin5.ApplyResourceChange_Response, error) {
   280  	resp := &tfplugin5.ApplyResourceChange_Response{}
   281  	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
   282  
   283  	priorStateVal, err := decodeDynamicValue(req.PriorState, ty)
   284  	if err != nil {
   285  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   286  		return resp, nil
   287  	}
   288  
   289  	plannedStateVal, err := decodeDynamicValue(req.PlannedState, ty)
   290  	if err != nil {
   291  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   292  		return resp, nil
   293  	}
   294  
   295  	configVal, err := decodeDynamicValue(req.Config, ty)
   296  	if err != nil {
   297  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   298  		return resp, nil
   299  	}
   300  
   301  	metaTy := p.schema.ProviderMeta.Block.ImpliedType()
   302  	metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy)
   303  	if err != nil {
   304  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   305  		return resp, nil
   306  	}
   307  
   308  	applyResp := p.provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
   309  		TypeName:       req.TypeName,
   310  		PriorState:     priorStateVal,
   311  		PlannedState:   plannedStateVal,
   312  		Config:         configVal,
   313  		PlannedPrivate: req.PlannedPrivate,
   314  		ProviderMeta:   metaVal,
   315  	})
   316  
   317  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, applyResp.Diagnostics)
   318  	if applyResp.Diagnostics.HasErrors() {
   319  		return resp, nil
   320  	}
   321  	resp.Private = applyResp.Private
   322  
   323  	resp.NewState, err = encodeDynamicValue(applyResp.NewState, ty)
   324  	if err != nil {
   325  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   326  		return resp, nil
   327  	}
   328  
   329  	return resp, nil
   330  }
   331  
   332  func (p *provider) ImportResourceState(_ context.Context, req *tfplugin5.ImportResourceState_Request) (*tfplugin5.ImportResourceState_Response, error) {
   333  	resp := &tfplugin5.ImportResourceState_Response{}
   334  
   335  	importResp := p.provider.ImportResourceState(providers.ImportResourceStateRequest{
   336  		TypeName: req.TypeName,
   337  		ID:       req.Id,
   338  	})
   339  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, importResp.Diagnostics)
   340  
   341  	for _, res := range importResp.ImportedResources {
   342  		ty := p.schema.ResourceTypes[res.TypeName].Block.ImpliedType()
   343  		state, err := encodeDynamicValue(res.State, ty)
   344  		if err != nil {
   345  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   346  			continue
   347  		}
   348  
   349  		resp.ImportedResources = append(resp.ImportedResources, &tfplugin5.ImportResourceState_ImportedResource{
   350  			TypeName: res.TypeName,
   351  			State:    state,
   352  			Private:  res.Private,
   353  		})
   354  	}
   355  
   356  	return resp, nil
   357  }
   358  
   359  func (p *provider) MoveResourceState(context.Context, *tfplugin5.MoveResourceState_Request) (*tfplugin5.MoveResourceState_Response, error) {
   360  	panic("Not Implemented")
   361  }
   362  
   363  func (p *provider) ReadDataSource(_ context.Context, req *tfplugin5.ReadDataSource_Request) (*tfplugin5.ReadDataSource_Response, error) {
   364  	resp := &tfplugin5.ReadDataSource_Response{}
   365  	ty := p.schema.DataSources[req.TypeName].Block.ImpliedType()
   366  
   367  	configVal, err := decodeDynamicValue(req.Config, ty)
   368  	if err != nil {
   369  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   370  		return resp, nil
   371  	}
   372  
   373  	metaTy := p.schema.ProviderMeta.Block.ImpliedType()
   374  	metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy)
   375  	if err != nil {
   376  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   377  		return resp, nil
   378  	}
   379  
   380  	readResp := p.provider.ReadDataSource(providers.ReadDataSourceRequest{
   381  		TypeName:     req.TypeName,
   382  		Config:       configVal,
   383  		ProviderMeta: metaVal,
   384  	})
   385  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics)
   386  	if readResp.Diagnostics.HasErrors() {
   387  		return resp, nil
   388  	}
   389  
   390  	resp.State, err = encodeDynamicValue(readResp.State, ty)
   391  	if err != nil {
   392  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   393  		return resp, nil
   394  	}
   395  
   396  	return resp, nil
   397  }
   398  
   399  func (p *provider) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) {
   400  	resp := &tfplugin5.Stop_Response{}
   401  	err := p.provider.Stop()
   402  	if err != nil {
   403  		resp.Error = err.Error()
   404  	}
   405  	return resp, nil
   406  }
   407  
   408  func (p *provider) GetFunctions(context.Context, *tfplugin5.GetFunctions_Request) (*tfplugin5.GetFunctions_Response, error) {
   409  	panic("Not Implemented")
   410  }
   411  
   412  func (p *provider) CallFunction(context.Context, *tfplugin5.CallFunction_Request) (*tfplugin5.CallFunction_Response, error) {
   413  	panic("Not Implemented")
   414  }
   415  
   416  // decode a DynamicValue from either the JSON or MsgPack encoding.
   417  func decodeDynamicValue(v *tfplugin5.DynamicValue, ty cty.Type) (cty.Value, error) {
   418  	// always return a valid value
   419  	var err error
   420  	res := cty.NullVal(ty)
   421  	if v == nil {
   422  		return res, nil
   423  	}
   424  
   425  	switch {
   426  	case len(v.Msgpack) > 0:
   427  		res, err = msgpack.Unmarshal(v.Msgpack, ty)
   428  	case len(v.Json) > 0:
   429  		res, err = ctyjson.Unmarshal(v.Json, ty)
   430  	}
   431  	return res, err
   432  }
   433  
   434  // encode a cty.Value into a DynamicValue msgpack payload.
   435  func encodeDynamicValue(v cty.Value, ty cty.Type) (*tfplugin5.DynamicValue, error) {
   436  	mp, err := msgpack.Marshal(v, ty)
   437  	return &tfplugin5.DynamicValue{
   438  		Msgpack: mp,
   439  	}, err
   440  }