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 }