github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/grpcwrap/provider.go (about)

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