github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/plugin/grpc_provider.go (about) 1 package plugin 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 "github.com/cycloidio/terraform/logging" 12 "github.com/cycloidio/terraform/plugin/convert" 13 "github.com/cycloidio/terraform/providers" 14 proto "github.com/cycloidio/terraform/tfplugin5" 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() proto.ProviderServer 26 } 27 28 func (p *GRPCProviderPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { 29 return &GRPCProvider{ 30 client: proto.NewProviderClient(c), 31 ctx: ctx, 32 }, nil 33 } 34 35 func (p *GRPCProviderPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { 36 proto.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 proto.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 // getSchema is used internally to get the saved provider schema. The schema 67 // should have already been fetched from the provider, but we have to 68 // synchronize access to avoid being called concurrently with GetSchema. 69 func (p *GRPCProvider) getSchema() providers.GetProviderSchemaResponse { 70 p.mu.Lock() 71 // unlock inline in case GetSchema needs to be called 72 if p.schemas.Provider.Block != nil { 73 p.mu.Unlock() 74 return p.schemas 75 } 76 p.mu.Unlock() 77 78 // the schema should have been fetched already, but give it another shot 79 // just in case things are being called out of order. This may happen for 80 // tests. 81 schemas := p.GetProviderSchema() 82 if schemas.Diagnostics.HasErrors() { 83 panic(schemas.Diagnostics.Err()) 84 } 85 86 return schemas 87 } 88 89 // getResourceSchema is a helper to extract the schema for a resource, and 90 // panics if the schema is not available. 91 func (p *GRPCProvider) getResourceSchema(name string) providers.Schema { 92 schema := p.getSchema() 93 resSchema, ok := schema.ResourceTypes[name] 94 if !ok { 95 panic("unknown resource type " + name) 96 } 97 return resSchema 98 } 99 100 // gettDatasourceSchema is a helper to extract the schema for a datasource, and 101 // panics if that schema is not available. 102 func (p *GRPCProvider) getDatasourceSchema(name string) providers.Schema { 103 schema := p.getSchema() 104 dataSchema, ok := schema.DataSources[name] 105 if !ok { 106 panic("unknown data source " + name) 107 } 108 return dataSchema 109 } 110 111 // getProviderMetaSchema is a helper to extract the schema for the meta info 112 // defined for a provider, 113 func (p *GRPCProvider) getProviderMetaSchema() providers.Schema { 114 schema := p.getSchema() 115 return schema.ProviderMeta 116 } 117 118 func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResponse) { 119 logger.Trace("GRPCProvider: GetProviderSchema") 120 p.mu.Lock() 121 defer p.mu.Unlock() 122 123 if p.schemas.Provider.Block != nil { 124 return p.schemas 125 } 126 127 resp.ResourceTypes = make(map[string]providers.Schema) 128 resp.DataSources = make(map[string]providers.Schema) 129 130 // Some providers may generate quite large schemas, and the internal default 131 // grpc response size limit is 4MB. 64MB should cover most any use case, and 132 // if we get providers nearing that we may want to consider a finer-grained 133 // API to fetch individual resource schemas. 134 // Note: this option is marked as EXPERIMENTAL in the grpc API. 135 const maxRecvSize = 64 << 20 136 protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize}) 137 if err != nil { 138 resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) 139 return resp 140 } 141 142 resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) 143 144 if protoResp.Provider == nil { 145 resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing provider schema")) 146 return resp 147 } 148 149 resp.Provider = convert.ProtoToProviderSchema(protoResp.Provider) 150 if protoResp.ProviderMeta == nil { 151 logger.Debug("No provider meta schema returned") 152 } else { 153 resp.ProviderMeta = convert.ProtoToProviderSchema(protoResp.ProviderMeta) 154 } 155 156 for name, res := range protoResp.ResourceSchemas { 157 resp.ResourceTypes[name] = convert.ProtoToProviderSchema(res) 158 } 159 160 for name, data := range protoResp.DataSourceSchemas { 161 resp.DataSources[name] = convert.ProtoToProviderSchema(data) 162 } 163 164 p.schemas = resp 165 166 return resp 167 } 168 169 func (p *GRPCProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { 170 logger.Trace("GRPCProvider: ValidateProviderConfig") 171 172 schema := p.getSchema() 173 ty := schema.Provider.Block.ImpliedType() 174 175 mp, err := msgpack.Marshal(r.Config, ty) 176 if err != nil { 177 resp.Diagnostics = resp.Diagnostics.Append(err) 178 return resp 179 } 180 181 protoReq := &proto.PrepareProviderConfig_Request{ 182 Config: &proto.DynamicValue{Msgpack: mp}, 183 } 184 185 protoResp, err := p.client.PrepareProviderConfig(p.ctx, protoReq) 186 if err != nil { 187 resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) 188 return resp 189 } 190 191 config, err := decodeDynamicValue(protoResp.PreparedConfig, ty) 192 if err != nil { 193 resp.Diagnostics = resp.Diagnostics.Append(err) 194 return resp 195 } 196 resp.PreparedConfig = config 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: 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 := &proto.ValidateResourceTypeConfig_Request{ 213 TypeName: r.TypeName, 214 Config: &proto.DynamicValue{Msgpack: mp}, 215 } 216 217 protoResp, err := p.client.ValidateResourceTypeConfig(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: 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 := &proto.ValidateDataSourceConfig_Request{ 239 TypeName: r.TypeName, 240 Config: &proto.DynamicValue{Msgpack: mp}, 241 } 242 243 protoResp, err := p.client.ValidateDataSourceConfig(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: UpgradeResourceState") 254 255 resSchema := p.getResourceSchema(r.TypeName) 256 257 protoReq := &proto.UpgradeResourceState_Request{ 258 TypeName: r.TypeName, 259 Version: int64(r.Version), 260 RawState: &proto.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: 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 := &proto.Configure_Request{ 304 TerraformVersion: r.TerraformVersion, 305 Config: &proto.DynamicValue{ 306 Msgpack: mp, 307 }, 308 } 309 310 protoResp, err := p.client.Configure(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: Stop") 321 322 resp, err := p.client.Stop(p.ctx, new(proto.Stop_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: 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 := &proto.ReadResource_Request{ 346 TypeName: r.TypeName, 347 CurrentState: &proto.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 = &proto.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: 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 := &proto.PlanResourceChange_Request{ 403 TypeName: r.TypeName, 404 PriorState: &proto.DynamicValue{Msgpack: priorMP}, 405 Config: &proto.DynamicValue{Msgpack: configMP}, 406 ProposedNewState: &proto.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 = &proto.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 resp.LegacyTypeSystem = protoResp.LegacyTypeSystem 440 441 return resp 442 } 443 444 func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 445 logger.Trace("GRPCProvider: ApplyResourceChange") 446 447 resSchema := p.getResourceSchema(r.TypeName) 448 metaSchema := p.getProviderMetaSchema() 449 450 priorMP, err := msgpack.Marshal(r.PriorState, resSchema.Block.ImpliedType()) 451 if err != nil { 452 resp.Diagnostics = resp.Diagnostics.Append(err) 453 return resp 454 } 455 plannedMP, err := msgpack.Marshal(r.PlannedState, resSchema.Block.ImpliedType()) 456 if err != nil { 457 resp.Diagnostics = resp.Diagnostics.Append(err) 458 return resp 459 } 460 configMP, err := msgpack.Marshal(r.Config, resSchema.Block.ImpliedType()) 461 if err != nil { 462 resp.Diagnostics = resp.Diagnostics.Append(err) 463 return resp 464 } 465 466 protoReq := &proto.ApplyResourceChange_Request{ 467 TypeName: r.TypeName, 468 PriorState: &proto.DynamicValue{Msgpack: priorMP}, 469 PlannedState: &proto.DynamicValue{Msgpack: plannedMP}, 470 Config: &proto.DynamicValue{Msgpack: configMP}, 471 PlannedPrivate: r.PlannedPrivate, 472 } 473 474 if metaSchema.Block != nil { 475 metaMP, err := msgpack.Marshal(r.ProviderMeta, metaSchema.Block.ImpliedType()) 476 if err != nil { 477 resp.Diagnostics = resp.Diagnostics.Append(err) 478 return resp 479 } 480 protoReq.ProviderMeta = &proto.DynamicValue{Msgpack: metaMP} 481 } 482 483 protoResp, err := p.client.ApplyResourceChange(p.ctx, protoReq) 484 if err != nil { 485 resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) 486 return resp 487 } 488 resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) 489 490 resp.Private = protoResp.Private 491 492 state, err := decodeDynamicValue(protoResp.NewState, resSchema.Block.ImpliedType()) 493 if err != nil { 494 resp.Diagnostics = resp.Diagnostics.Append(err) 495 return resp 496 } 497 resp.NewState = state 498 499 resp.LegacyTypeSystem = protoResp.LegacyTypeSystem 500 501 return resp 502 } 503 504 func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) { 505 logger.Trace("GRPCProvider: ImportResourceState") 506 507 protoReq := &proto.ImportResourceState_Request{ 508 TypeName: r.TypeName, 509 Id: r.ID, 510 } 511 512 protoResp, err := p.client.ImportResourceState(p.ctx, protoReq) 513 if err != nil { 514 resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) 515 return resp 516 } 517 resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) 518 519 for _, imported := range protoResp.ImportedResources { 520 resource := providers.ImportedResource{ 521 TypeName: imported.TypeName, 522 Private: imported.Private, 523 } 524 525 resSchema := p.getResourceSchema(resource.TypeName) 526 state, err := decodeDynamicValue(imported.State, resSchema.Block.ImpliedType()) 527 if err != nil { 528 resp.Diagnostics = resp.Diagnostics.Append(err) 529 return resp 530 } 531 resource.State = state 532 resp.ImportedResources = append(resp.ImportedResources, resource) 533 } 534 535 return resp 536 } 537 538 func (p *GRPCProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { 539 logger.Trace("GRPCProvider: ReadDataSource") 540 541 dataSchema := p.getDatasourceSchema(r.TypeName) 542 metaSchema := p.getProviderMetaSchema() 543 544 config, err := msgpack.Marshal(r.Config, dataSchema.Block.ImpliedType()) 545 if err != nil { 546 resp.Diagnostics = resp.Diagnostics.Append(err) 547 return resp 548 } 549 550 protoReq := &proto.ReadDataSource_Request{ 551 TypeName: r.TypeName, 552 Config: &proto.DynamicValue{ 553 Msgpack: config, 554 }, 555 } 556 557 if metaSchema.Block != nil { 558 metaMP, err := msgpack.Marshal(r.ProviderMeta, metaSchema.Block.ImpliedType()) 559 if err != nil { 560 resp.Diagnostics = resp.Diagnostics.Append(err) 561 return resp 562 } 563 protoReq.ProviderMeta = &proto.DynamicValue{Msgpack: metaMP} 564 } 565 566 protoResp, err := p.client.ReadDataSource(p.ctx, protoReq) 567 if err != nil { 568 resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) 569 return resp 570 } 571 resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) 572 573 state, err := decodeDynamicValue(protoResp.State, dataSchema.Block.ImpliedType()) 574 if err != nil { 575 resp.Diagnostics = resp.Diagnostics.Append(err) 576 return resp 577 } 578 resp.State = state 579 580 return resp 581 } 582 583 // closing the grpc connection is final, and terraform will call it at the end of every phase. 584 func (p *GRPCProvider) Close() error { 585 logger.Trace("GRPCProvider: Close") 586 587 // Make sure to stop the server if we're not running within go-plugin. 588 if p.TestServer != nil { 589 p.TestServer.Stop() 590 } 591 592 // Check this since it's not automatically inserted during plugin creation. 593 // It's currently only inserted by the command package, because that is 594 // where the factory is built and is the only point with access to the 595 // plugin.Client. 596 if p.PluginClient == nil { 597 logger.Debug("provider has no plugin.Client") 598 return nil 599 } 600 601 p.PluginClient.Kill() 602 return nil 603 } 604 605 // Decode a DynamicValue from either the JSON or MsgPack encoding. 606 func decodeDynamicValue(v *proto.DynamicValue, ty cty.Type) (cty.Value, error) { 607 // always return a valid value 608 var err error 609 res := cty.NullVal(ty) 610 if v == nil { 611 return res, nil 612 } 613 614 switch { 615 case len(v.Msgpack) > 0: 616 res, err = msgpack.Unmarshal(v.Msgpack, ty) 617 case len(v.Json) > 0: 618 res, err = ctyjson.Unmarshal(v.Json, ty) 619 } 620 return res, err 621 }