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