github.com/hashicorp/terraform-plugin-sdk@v1.17.2/plugin/grpc_provider.go (about)

     1  package plugin
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"log"
     7  	"sync"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	plugin "github.com/hashicorp/go-plugin"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/plugin/convert"
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
    14  	proto "github.com/hashicorp/terraform-plugin-sdk/internal/tfplugin5"
    15  	"github.com/zclconf/go-cty/cty/msgpack"
    16  	"google.golang.org/grpc"
    17  )
    18  
    19  // GRPCProviderPlugin implements plugin.GRPCPlugin for the go-plugin package.
    20  type GRPCProviderPlugin struct {
    21  	plugin.Plugin
    22  	GRPCProvider func() proto.ProviderServer
    23  }
    24  
    25  func (p *GRPCProviderPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
    26  	return &GRPCProvider{
    27  		client: proto.NewProviderClient(c),
    28  		ctx:    ctx,
    29  	}, nil
    30  }
    31  
    32  func (p *GRPCProviderPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
    33  	proto.RegisterProviderServer(s, p.GRPCProvider())
    34  	return nil
    35  }
    36  
    37  // GRPCProvider handles the client, or core side of the plugin rpc connection.
    38  // The GRPCProvider methods are mostly a translation layer between the
    39  // terraform provioders types and the grpc proto types, directly converting
    40  // between the two.
    41  type GRPCProvider struct {
    42  	// PluginClient provides a reference to the plugin.Client which controls the plugin process.
    43  	// This allows the GRPCProvider a way to shutdown the plugin process.
    44  	PluginClient *plugin.Client
    45  
    46  	// TestServer contains a grpc.Server to close when the GRPCProvider is being
    47  	// used in an end to end test of a provider.
    48  	TestServer *grpc.Server
    49  
    50  	// Proto client use to make the grpc service calls.
    51  	client proto.ProviderClient
    52  
    53  	// this context is created by the plugin package, and is canceled when the
    54  	// plugin process ends.
    55  	ctx context.Context
    56  
    57  	// schema stores the schema for this provider. This is used to properly
    58  	// serialize the state for requests.
    59  	mu      sync.Mutex
    60  	schemas providers.GetSchemaResponse
    61  }
    62  
    63  // getSchema is used internally to get the saved provider schema.  The schema
    64  // should have already been fetched from the provider, but we have to
    65  // synchronize access to avoid being called concurrently with GetSchema.
    66  func (p *GRPCProvider) getSchema() providers.GetSchemaResponse {
    67  	p.mu.Lock()
    68  	// unlock inline in case GetSchema needs to be called
    69  	if p.schemas.Provider.Block != nil {
    70  		p.mu.Unlock()
    71  		return p.schemas
    72  	}
    73  	p.mu.Unlock()
    74  
    75  	// the schema should have been fetched already, but give it another shot
    76  	// just in case things are being called out of order. This may happen for
    77  	// tests.
    78  	schemas := p.GetSchema()
    79  	if schemas.Diagnostics.HasErrors() {
    80  		panic(schemas.Diagnostics.Err())
    81  	}
    82  
    83  	return schemas
    84  }
    85  
    86  // getResourceSchema is a helper to extract the schema for a resource, and
    87  // panics if the schema is not available.
    88  func (p *GRPCProvider) getResourceSchema(name string) providers.Schema {
    89  	schema := p.getSchema()
    90  	resSchema, ok := schema.ResourceTypes[name]
    91  	if !ok {
    92  		panic("unknown resource type " + name)
    93  	}
    94  	return resSchema
    95  }
    96  
    97  // gettDatasourceSchema is a helper to extract the schema for a datasource, and
    98  // panics if that schema is not available.
    99  func (p *GRPCProvider) getDatasourceSchema(name string) providers.Schema {
   100  	schema := p.getSchema()
   101  	dataSchema, ok := schema.DataSources[name]
   102  	if !ok {
   103  		panic("unknown data source " + name)
   104  	}
   105  	return dataSchema
   106  }
   107  
   108  func (p *GRPCProvider) GetSchema() (resp providers.GetSchemaResponse) {
   109  	log.Printf("[TRACE] GRPCProvider: GetSchema")
   110  	p.mu.Lock()
   111  	defer p.mu.Unlock()
   112  
   113  	if p.schemas.Provider.Block != nil {
   114  		return p.schemas
   115  	}
   116  
   117  	resp.ResourceTypes = make(map[string]providers.Schema)
   118  	resp.DataSources = make(map[string]providers.Schema)
   119  
   120  	// Some providers may generate quite large schemas, and the internal default
   121  	// grpc response size limit is 4MB. 64MB should cover most any use case, and
   122  	// if we get providers nearing that we may want to consider a finer-grained
   123  	// API to fetch individual resource schemas.
   124  	// Note: this option is marked as EXPERIMENTAL in the grpc API.
   125  	const maxRecvSize = 64 << 20
   126  	protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize})
   127  	if err != nil {
   128  		resp.Diagnostics = resp.Diagnostics.Append(err)
   129  		return resp
   130  	}
   131  
   132  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   133  
   134  	if protoResp.Provider == nil {
   135  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing provider schema"))
   136  		return resp
   137  	}
   138  
   139  	resp.Provider = convert.ProtoToProviderSchema(protoResp.Provider)
   140  
   141  	for name, res := range protoResp.ResourceSchemas {
   142  		resp.ResourceTypes[name] = convert.ProtoToProviderSchema(res)
   143  	}
   144  
   145  	for name, data := range protoResp.DataSourceSchemas {
   146  		resp.DataSources[name] = convert.ProtoToProviderSchema(data)
   147  	}
   148  
   149  	p.schemas = resp
   150  
   151  	return resp
   152  }
   153  
   154  func (p *GRPCProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) (resp providers.PrepareProviderConfigResponse) {
   155  	log.Printf("[TRACE] GRPCProvider: PrepareProviderConfig")
   156  
   157  	schema := p.getSchema()
   158  	ty := schema.Provider.Block.ImpliedType()
   159  
   160  	mp, err := msgpack.Marshal(r.Config, ty)
   161  	if err != nil {
   162  		resp.Diagnostics = resp.Diagnostics.Append(err)
   163  		return resp
   164  	}
   165  
   166  	protoReq := &proto.PrepareProviderConfig_Request{
   167  		Config: &proto.DynamicValue{Msgpack: mp},
   168  	}
   169  
   170  	protoResp, err := p.client.PrepareProviderConfig(p.ctx, protoReq)
   171  	if err != nil {
   172  		resp.Diagnostics = resp.Diagnostics.Append(err)
   173  		return resp
   174  	}
   175  
   176  	config := cty.NullVal(ty)
   177  	if protoResp.PreparedConfig != nil {
   178  		config, err = msgpack.Unmarshal(protoResp.PreparedConfig.Msgpack, ty)
   179  		if err != nil {
   180  			resp.Diagnostics = resp.Diagnostics.Append(err)
   181  			return resp
   182  		}
   183  	}
   184  	resp.PreparedConfig = config
   185  
   186  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   187  	return resp
   188  }
   189  
   190  func (p *GRPCProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) (resp providers.ValidateResourceTypeConfigResponse) {
   191  	log.Printf("[TRACE] GRPCProvider: ValidateResourceTypeConfig")
   192  	resourceSchema := p.getResourceSchema(r.TypeName)
   193  
   194  	mp, err := msgpack.Marshal(r.Config, resourceSchema.Block.ImpliedType())
   195  	if err != nil {
   196  		resp.Diagnostics = resp.Diagnostics.Append(err)
   197  		return resp
   198  	}
   199  
   200  	protoReq := &proto.ValidateResourceTypeConfig_Request{
   201  		TypeName: r.TypeName,
   202  		Config:   &proto.DynamicValue{Msgpack: mp},
   203  	}
   204  
   205  	protoResp, err := p.client.ValidateResourceTypeConfig(p.ctx, protoReq)
   206  	if err != nil {
   207  		resp.Diagnostics = resp.Diagnostics.Append(err)
   208  		return resp
   209  	}
   210  
   211  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   212  	return resp
   213  }
   214  
   215  func (p *GRPCProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) (resp providers.ValidateDataSourceConfigResponse) {
   216  	log.Printf("[TRACE] GRPCProvider: ValidateDataSourceConfig")
   217  
   218  	dataSchema := p.getDatasourceSchema(r.TypeName)
   219  
   220  	mp, err := msgpack.Marshal(r.Config, dataSchema.Block.ImpliedType())
   221  	if err != nil {
   222  		resp.Diagnostics = resp.Diagnostics.Append(err)
   223  		return resp
   224  	}
   225  
   226  	protoReq := &proto.ValidateDataSourceConfig_Request{
   227  		TypeName: r.TypeName,
   228  		Config:   &proto.DynamicValue{Msgpack: mp},
   229  	}
   230  
   231  	protoResp, err := p.client.ValidateDataSourceConfig(p.ctx, protoReq)
   232  	if err != nil {
   233  		resp.Diagnostics = resp.Diagnostics.Append(err)
   234  		return resp
   235  	}
   236  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   237  	return resp
   238  }
   239  
   240  func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
   241  	log.Printf("[TRACE] GRPCProvider: UpgradeResourceState")
   242  
   243  	resSchema := p.getResourceSchema(r.TypeName)
   244  
   245  	protoReq := &proto.UpgradeResourceState_Request{
   246  		TypeName: r.TypeName,
   247  		Version:  int64(r.Version),
   248  		RawState: &proto.RawState{
   249  			Json:    r.RawStateJSON,
   250  			Flatmap: r.RawStateFlatmap,
   251  		},
   252  	}
   253  
   254  	protoResp, err := p.client.UpgradeResourceState(p.ctx, protoReq)
   255  	if err != nil {
   256  		resp.Diagnostics = resp.Diagnostics.Append(err)
   257  		return resp
   258  	}
   259  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   260  
   261  	state := cty.NullVal(resSchema.Block.ImpliedType())
   262  	if protoResp.UpgradedState != nil {
   263  		state, err = msgpack.Unmarshal(protoResp.UpgradedState.Msgpack, resSchema.Block.ImpliedType())
   264  		if err != nil {
   265  			resp.Diagnostics = resp.Diagnostics.Append(err)
   266  			return resp
   267  		}
   268  	}
   269  
   270  	resp.UpgradedState = state
   271  	return resp
   272  }
   273  
   274  func (p *GRPCProvider) Configure(r providers.ConfigureRequest) (resp providers.ConfigureResponse) {
   275  	log.Printf("[TRACE] GRPCProvider: Configure")
   276  
   277  	schema := p.getSchema()
   278  
   279  	var mp []byte
   280  
   281  	// we don't have anything to marshal if there's no config
   282  	mp, err := msgpack.Marshal(r.Config, schema.Provider.Block.ImpliedType())
   283  	if err != nil {
   284  		resp.Diagnostics = resp.Diagnostics.Append(err)
   285  		return resp
   286  	}
   287  
   288  	protoReq := &proto.Configure_Request{
   289  		TerraformVersion: r.TerraformVersion,
   290  		Config: &proto.DynamicValue{
   291  			Msgpack: mp,
   292  		},
   293  	}
   294  
   295  	protoResp, err := p.client.Configure(p.ctx, protoReq)
   296  	if err != nil {
   297  		resp.Diagnostics = resp.Diagnostics.Append(err)
   298  		return resp
   299  	}
   300  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   301  	return resp
   302  }
   303  
   304  func (p *GRPCProvider) Stop() error {
   305  	log.Printf("[TRACE] GRPCProvider: Stop")
   306  
   307  	resp, err := p.client.Stop(p.ctx, new(proto.Stop_Request))
   308  	if err != nil {
   309  		return err
   310  	}
   311  
   312  	if resp.Error != "" {
   313  		return errors.New(resp.Error)
   314  	}
   315  	return nil
   316  }
   317  
   318  func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   319  	log.Printf("[TRACE] GRPCProvider: ReadResource")
   320  
   321  	resSchema := p.getResourceSchema(r.TypeName)
   322  
   323  	mp, err := msgpack.Marshal(r.PriorState, resSchema.Block.ImpliedType())
   324  	if err != nil {
   325  		resp.Diagnostics = resp.Diagnostics.Append(err)
   326  		return resp
   327  	}
   328  
   329  	protoReq := &proto.ReadResource_Request{
   330  		TypeName:     r.TypeName,
   331  		CurrentState: &proto.DynamicValue{Msgpack: mp},
   332  		Private:      r.Private,
   333  	}
   334  
   335  	protoResp, err := p.client.ReadResource(p.ctx, protoReq)
   336  	if err != nil {
   337  		resp.Diagnostics = resp.Diagnostics.Append(err)
   338  		return resp
   339  	}
   340  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   341  
   342  	state := cty.NullVal(resSchema.Block.ImpliedType())
   343  	if protoResp.NewState != nil {
   344  		state, err = msgpack.Unmarshal(protoResp.NewState.Msgpack, resSchema.Block.ImpliedType())
   345  		if err != nil {
   346  			resp.Diagnostics = resp.Diagnostics.Append(err)
   347  			return resp
   348  		}
   349  	}
   350  	resp.NewState = state
   351  	resp.Private = protoResp.Private
   352  
   353  	return resp
   354  }
   355  
   356  func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   357  	log.Printf("[TRACE] GRPCProvider: PlanResourceChange")
   358  
   359  	resSchema := p.getResourceSchema(r.TypeName)
   360  
   361  	priorMP, err := msgpack.Marshal(r.PriorState, resSchema.Block.ImpliedType())
   362  	if err != nil {
   363  		resp.Diagnostics = resp.Diagnostics.Append(err)
   364  		return resp
   365  	}
   366  
   367  	configMP, err := msgpack.Marshal(r.Config, resSchema.Block.ImpliedType())
   368  	if err != nil {
   369  		resp.Diagnostics = resp.Diagnostics.Append(err)
   370  		return resp
   371  	}
   372  
   373  	propMP, err := msgpack.Marshal(r.ProposedNewState, resSchema.Block.ImpliedType())
   374  	if err != nil {
   375  		resp.Diagnostics = resp.Diagnostics.Append(err)
   376  		return resp
   377  	}
   378  
   379  	protoReq := &proto.PlanResourceChange_Request{
   380  		TypeName:         r.TypeName,
   381  		PriorState:       &proto.DynamicValue{Msgpack: priorMP},
   382  		Config:           &proto.DynamicValue{Msgpack: configMP},
   383  		ProposedNewState: &proto.DynamicValue{Msgpack: propMP},
   384  		PriorPrivate:     r.PriorPrivate,
   385  	}
   386  
   387  	protoResp, err := p.client.PlanResourceChange(p.ctx, protoReq)
   388  	if err != nil {
   389  		resp.Diagnostics = resp.Diagnostics.Append(err)
   390  		return resp
   391  	}
   392  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   393  
   394  	state := cty.NullVal(resSchema.Block.ImpliedType())
   395  	if protoResp.PlannedState != nil {
   396  		state, err = msgpack.Unmarshal(protoResp.PlannedState.Msgpack, resSchema.Block.ImpliedType())
   397  		if err != nil {
   398  			resp.Diagnostics = resp.Diagnostics.Append(err)
   399  			return resp
   400  		}
   401  	}
   402  	resp.PlannedState = state
   403  
   404  	for _, p := range protoResp.RequiresReplace {
   405  		resp.RequiresReplace = append(resp.RequiresReplace, convert.AttributePathToPath(p))
   406  	}
   407  
   408  	resp.PlannedPrivate = protoResp.PlannedPrivate
   409  
   410  	resp.LegacyTypeSystem = protoResp.LegacyTypeSystem
   411  
   412  	return resp
   413  }
   414  
   415  func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   416  	log.Printf("[TRACE] GRPCProvider: ApplyResourceChange")
   417  
   418  	resSchema := p.getResourceSchema(r.TypeName)
   419  
   420  	priorMP, err := msgpack.Marshal(r.PriorState, resSchema.Block.ImpliedType())
   421  	if err != nil {
   422  		resp.Diagnostics = resp.Diagnostics.Append(err)
   423  		return resp
   424  	}
   425  	plannedMP, err := msgpack.Marshal(r.PlannedState, resSchema.Block.ImpliedType())
   426  	if err != nil {
   427  		resp.Diagnostics = resp.Diagnostics.Append(err)
   428  		return resp
   429  	}
   430  	configMP, err := msgpack.Marshal(r.Config, resSchema.Block.ImpliedType())
   431  	if err != nil {
   432  		resp.Diagnostics = resp.Diagnostics.Append(err)
   433  		return resp
   434  	}
   435  
   436  	protoReq := &proto.ApplyResourceChange_Request{
   437  		TypeName:       r.TypeName,
   438  		PriorState:     &proto.DynamicValue{Msgpack: priorMP},
   439  		PlannedState:   &proto.DynamicValue{Msgpack: plannedMP},
   440  		Config:         &proto.DynamicValue{Msgpack: configMP},
   441  		PlannedPrivate: r.PlannedPrivate,
   442  	}
   443  
   444  	protoResp, err := p.client.ApplyResourceChange(p.ctx, protoReq)
   445  	if err != nil {
   446  		resp.Diagnostics = resp.Diagnostics.Append(err)
   447  		return resp
   448  	}
   449  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   450  
   451  	resp.Private = protoResp.Private
   452  
   453  	state := cty.NullVal(resSchema.Block.ImpliedType())
   454  	if protoResp.NewState != nil {
   455  		state, err = msgpack.Unmarshal(protoResp.NewState.Msgpack, resSchema.Block.ImpliedType())
   456  		if err != nil {
   457  			resp.Diagnostics = resp.Diagnostics.Append(err)
   458  			return resp
   459  		}
   460  	}
   461  	resp.NewState = state
   462  
   463  	resp.LegacyTypeSystem = protoResp.LegacyTypeSystem
   464  
   465  	return resp
   466  }
   467  
   468  func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) {
   469  	log.Printf("[TRACE] GRPCProvider: ImportResourceState")
   470  
   471  	protoReq := &proto.ImportResourceState_Request{
   472  		TypeName: r.TypeName,
   473  		Id:       r.ID,
   474  	}
   475  
   476  	protoResp, err := p.client.ImportResourceState(p.ctx, protoReq)
   477  	if err != nil {
   478  		resp.Diagnostics = resp.Diagnostics.Append(err)
   479  		return resp
   480  	}
   481  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   482  
   483  	for _, imported := range protoResp.ImportedResources {
   484  		resource := providers.ImportedResource{
   485  			TypeName: imported.TypeName,
   486  			Private:  imported.Private,
   487  		}
   488  
   489  		resSchema := p.getResourceSchema(resource.TypeName)
   490  		state := cty.NullVal(resSchema.Block.ImpliedType())
   491  		if imported.State != nil {
   492  			state, err = msgpack.Unmarshal(imported.State.Msgpack, resSchema.Block.ImpliedType())
   493  			if err != nil {
   494  				resp.Diagnostics = resp.Diagnostics.Append(err)
   495  				return resp
   496  			}
   497  		}
   498  		resource.State = state
   499  		resp.ImportedResources = append(resp.ImportedResources, resource)
   500  	}
   501  
   502  	return resp
   503  }
   504  
   505  func (p *GRPCProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   506  	log.Printf("[TRACE] GRPCProvider: ReadDataSource")
   507  
   508  	dataSchema := p.getDatasourceSchema(r.TypeName)
   509  
   510  	config, err := msgpack.Marshal(r.Config, dataSchema.Block.ImpliedType())
   511  	if err != nil {
   512  		resp.Diagnostics = resp.Diagnostics.Append(err)
   513  		return resp
   514  	}
   515  
   516  	protoReq := &proto.ReadDataSource_Request{
   517  		TypeName: r.TypeName,
   518  		Config: &proto.DynamicValue{
   519  			Msgpack: config,
   520  		},
   521  	}
   522  
   523  	protoResp, err := p.client.ReadDataSource(p.ctx, protoReq)
   524  	if err != nil {
   525  		resp.Diagnostics = resp.Diagnostics.Append(err)
   526  		return resp
   527  	}
   528  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   529  
   530  	state := cty.NullVal(dataSchema.Block.ImpliedType())
   531  	if protoResp.State != nil {
   532  		state, err = msgpack.Unmarshal(protoResp.State.Msgpack, dataSchema.Block.ImpliedType())
   533  		if err != nil {
   534  			resp.Diagnostics = resp.Diagnostics.Append(err)
   535  			return resp
   536  		}
   537  	}
   538  	resp.State = state
   539  
   540  	return resp
   541  }
   542  
   543  // closing the grpc connection is final, and terraform will call it at the end of every phase.
   544  func (p *GRPCProvider) Close() error {
   545  	log.Printf("[TRACE] GRPCProvider: Close")
   546  
   547  	// Make sure to stop the server if we're not running within go-plugin.
   548  	if p.TestServer != nil {
   549  		p.TestServer.Stop()
   550  	}
   551  
   552  	// Check this since it's not automatically inserted during plugin creation.
   553  	// It's currently only inserted by the command package, because that is
   554  	// where the factory is built and is the only point with access to the
   555  	// plugin.Client.
   556  	if p.PluginClient == nil {
   557  		log.Println("[DEBUG] provider has no plugin.Client")
   558  		return nil
   559  	}
   560  
   561  	p.PluginClient.Kill()
   562  	return nil
   563  }