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